What is the best practice when returning data from a function. Is it better to return null or an empty object? And why you need to use one option compared to another.

Consider the following option:

 public UserEntity GetUserById(Guid userId) { //Здесь код доступа к базе данных... //Проверяем вернувшиеся данные и возвращаем null если ничего не найдено if (!DataExists) return null; //Или же я должен вернуть пустой объект? //return new UserEntity(); else return existingUserEntity; } 

Transfer

  • @andreycha why c # tag removed? In the text of the question c # code. - Stack
  • one
    @Stack because the language is agnostic language and is suitable for any language that allows null references. And to .NET also has nothing to do. Will you now cling to me in all questions? :) - andreycha
  • The architecture tag, in my opinion, is too loud for such a question. Perhaps we would not have interfered with the best-practice tag. - andreycha
  • @andreycha "because language agnostic question" - well, so many questions where there is a c # tag can have this tag, or remove it altogether, or add typescript. because the syntax is similar) but this is wrong. if you make a mistake, you can fix what you have already done. no one personally clings to you. I had a page open - the tags were and disappeared. if someone else had done it, I would have written a comment in the same way. - Stack
  • one
    @andreycha We have the label "language-agnostic" called "any-language", but the problem is that this issue addresses the specific language of a specific version on a specific platform. In other languages ​​- other features, practices and traditions. My answer is completely unsuitable for languages ​​where the optional type and pattern matching are used. - Athari

5 answers 5

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.

    If you understand the Null Object pattern as an empty object, I would almost never recommend using it. The problem is that it allows you to suppress the error instead of checking it out. This pattern is not essentially different from the return of the error code, only the error code is combined with the returned object.

    In the case of a null return, code that does not check this by mistake will immediately fall in the very first test. If you return an artificial “empty object”, the code that does not check it will assume that it works correctly, and you will never find an error.

    The method must have a contract : whether the returned object has the right to “absent” or not. For the case when the absence of the returned object is normal, the expected case is to return null . For the case when the absence of the returned object means essentially an error, you need to throw an exception.

    Returning a collection from a method works essentially the same. If your method worked normally, and the resulting collection was empty (and this is not an error), you normally return a collection consisting of 0 elements, this is a normal case, successful execution of the function. If you have no result as a result of an error, and this is an expected case, then it makes sense to return null . If you have no result as a result of an error, and this case is not expected in principle, then you should throw an exception.

    • > For the case when the absence of the returned object means essentially an error, you need to throw an exception. Examples in the studio. - hellboy
    • @hellboy: Will getting an element from an array at the wrong index? - VladD

    Returning null seems like the best idea for when data is not available.

    Returning an empty object implies that the data was returned, while returning null makes it clear that nothing was returned.

    In addition, returning null can lead to a NullReferenceException when trying to access members of an object, which can be useful for detecting erroneous code. Access to members of an empty object will not lead to an error, and this error may remain undisclosed for a long time.

    Transfer

      return null or empty object?

      For reference types, they usually return null, and for value types, they return the 'empty object' Empty (defined in many structures in the .NET Framework).

      We must try not to return null, because this is causing a lot of bugs. Tony Hoare (added null to algol in 1965) in 2009 said that null reference
      - The Billion Dollar Mistake (billion dollar error).

      For example, if a method cannot return the requested object, then instead of returning null, you can use the Fail Fast principle. Or you need to override the method as Try *, for example, int.TryParse.

        Part of this problem can be solved with the Maybe monad, i.e. return something like:

         Maybe<User> GetUser(int id); 

        Or the monad With:

         string name = rep.GetUser(-1).With(u => u.Name); 

        You can use libraries from nuget (search by monads). Although this approach imposes obligations on his support throughout the model. Related Links: