La semplice risposta è ogni volta che un'operazione è impossibile (a causa di una delle due applicazioni o perché violerebbe la logica aziendale). Se viene invocato un metodo ed è impossibile fare ciò per cui è stato scritto il metodo, genera un'eccezione. Un buon esempio è che i costruttori generano sempre ArgumentExceptions se non è possibile creare un'istanza utilizzando i parametri forniti. Un altro esempio è InvalidOperationException, che viene generato quando un'operazione non può essere eseguita a causa dello stato di un altro membro o membri della classe.
Nel tuo caso, se viene richiamato un metodo come Login (nome utente, password), se il nome utente non è valido, è effettivamente corretto lanciare un UserNameNotValidException o PasswordNotCorrectException se la password non è corretta. L'utente non può accedere utilizzando i parametri forniti (ovvero è impossibile perché violerebbe l'autenticazione), quindi genera un'eccezione. Anche se potrei avere le tue due eccezioni ereditate da ArgumentException.
Detto questo, se si desidera NON generare un'eccezione perché un errore di accesso può essere molto comune, una strategia è invece quella di creare un metodo che restituisca tipi che rappresentano diversi errori. Ecco un esempio:
{ // class
...
public LoginResult Login(string user, string password)
{
if (IsInvalidUser(user))
{
return new UserInvalidLoginResult(user);
}
else if (IsInvalidPassword(user, password))
{
return new PasswordInvalidLoginResult(user, password);
}
else
{
return new SuccessfulLoginResult();
}
}
...
}
public abstract class LoginResult
{
public readonly string Message;
protected LoginResult(string message)
{
this.Message = message;
}
}
public class SuccessfulLoginResult : LoginResult
{
public SucccessfulLogin(string user)
: base(string.Format("Login for user '{0}' was successful.", user))
{ }
}
public class UserInvalidLoginResult : LoginResult
{
public UserInvalidLoginResult(string user)
: base(string.Format("The username '{0}' is invalid.", user))
{ }
}
public class PasswordInvalidLoginResult : LoginResult
{
public PasswordInvalidLoginResult(string password, string user)
: base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
{ }
}
Alla maggior parte degli sviluppatori viene insegnato a evitare le eccezioni a causa del sovraccarico causato dal loro lancio. È fantastico essere attenti alle risorse, ma di solito non a scapito della progettazione dell'applicazione. Questo è probabilmente il motivo per cui ti è stato detto di non gettare le tue due eccezioni. Se utilizzare le eccezioni o meno di solito si riduce alla frequenza con cui si verificherà l'eccezione. Se si tratta di un risultato abbastanza comune o abbastanza prevedibile, in questo caso la maggior parte degli sviluppatori eviterà le eccezioni e creerà invece un altro metodo per indicare il fallimento, a causa del presunto consumo di risorse.
Ecco un esempio di come evitare l'utilizzo di Eccezioni in uno scenario come appena descritto, usando il modello Try ():
public class ValidatedLogin
{
public readonly string User;
public readonly string Password;
public ValidatedLogin(string user, string password)
{
if (IsInvalidUser(user))
{
throw new UserInvalidException(user);
}
else if (IsInvalidPassword(user, password))
{
throw new PasswordInvalidException(password);
}
this.User = user;
this.Password = password;
}
public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
{
if (IsInvalidUser(user) ||
IsInvalidPassword(user, password))
{
return false;
}
validatedLogin = new ValidatedLogin(user, password);
return true;
}
}