The choice between null and a null object depends on how the method is used.
Examples
Receiving a collection of users
ICollection<User> GetAllUsers();
How will this method be used? Most likely, something like this:
foreach (User user in userService.GetAllUsers()) { // Обработка или отображение конкретного юзера }
Returning an empty collection in this case will allow us to save on one if ( foreach cannot ignore null ). We don't care if there were any elements in the collection or not, the logic of the calling method usually does not depend on it. If it does, we can always use the Count property.
Getting one user
User GetUser(int id);
How will this method be used? Most likely, something like this:
User user = userService.GetUser(id); if (user == null) { // Всё плохо, обработать ошибку } else { // Обработать или отобразить пользователя }
In this case, it is reasonable to return null , because the presence and absence of a result implies different behavior of the calling code.
What exactly should not be done is to return new User() : this will not allow to check if the requested user exists or not. If we do null-objects, then they should, if possible, be in a single copy and immutable.
Getting current user
User GetCurrentUser();
How will this method be used? Most likely, something like this:
User currentUser = userService.GetCurrentUser(); Console.WriteLine("{0} ({1})", currentUser.Name, currentUser.Level);
In this case, user processing usually does not depend on the presence or absence of a real user, so you can return a user-guest object. If verification is required, then you can implement a property like User.IsRegistered or User.AccessLevel .
Future plans
In C # 7, it is planned to add a separation between nullable and non-nullable for reference types (in addition to value types). Then the methods above will look like this:
ICollection<User> GetAllUsers(); User? GetUser(int id); // Обратите внимание на знак вопроса User GetCurrentUser();
If you call the GetUser method and do not check for null , the compiler will give a warning.
ReSharper
If you have R #, then you can add annotations now:
[NotNull, ItemNotNull] ICollection<User> GetAllUsers(); [CanBeNull] User GetUser(int id); [NotNull] User GetCurrentUser();
These annotations will allow the R # analyzer to clearly distinguish what may be null and what cannot, and thereby warn the user about potential errors.