0

Probably the answer is you can't. However, I would like to have a work-around to solve my problem.

Objective

I am trying to create a program in which I try to avoid nulls as much as possible.

Problem

I am using the empty object pattern in places where null could have been returned. Like this:

public User GetByEmail(string email){
   //search user on database given unique email
   if(!WasFound())
      return User.Empty;
}

and here is the User Domain model.

//User has more fields but for simplicity I omitted them
public class User {
   public User(string username,string email){
      this.Username = username;
      this.Email = email;
   }

   //...

   //probaly not the best default values but I trivially chose them for explanation.
   private static readonly User _empty = new User("*","na@empty.com");
   public static User Empty{
      get{return _empty;}
   }
}

So far so good. However, I am planning that the static Empty function be implemented by the majority of my domain models. Also, Someone else might create a domain and I want to force them to implement the Empty function. So I created a DataModel class that will be the base of all domain Models.

public abstract class DataModel<TModel>: BaseEntity, IEmptiable<TModel> where TModel : new()
    {
        private bool _isEmpty = false;

        public virtual static TModel Empty {
            get {
                _isEmpty = true;
                return CreateEmpty();
            }
        }

        public virtual bool IsEmpty() => _isEmpty;

        protected abstract TModel CreateEmpty();
    }

And my new User class would look like this.

public class User: DataModel<User> {
   public User(string username,string email){
      this.Username = username;
      this.Email = email;
   }

   //...

   private readonly User _empty = new User("*",na@empty.com);
   protected User CreateEmpty(){
      return _empty;
   }
}

In that way I am forcing the developer to implement a CreateEmpty() class and the Empty function will be present in the domain if you extend the DataModel abstract class. My problem is that Empty function is static and is giving me many problems. Is there a work-around to this problem? My vision is to be able to execute something like:

Account.Empty
//and to check if is empty with
if(Account.IsEmpty())
  //Plan B

Keep in mind that this is pseudocode. (I know that static function can't user non-static variables declared outside of the function.)

Any help will be much appreciated.

  • 1
    That seems a lot of work and in the end you end up with the same if statement in your code. Are you sure that much added complexity is called for, just because .IsEmpty() looks better than == null? Because if you still have that if statement, that's all it is. – nvoigt Jan 4 at 10:53
  • I am not trying to avoid the if statement. What I am trying to avoid is an exception. If someone forgets to check for null it will throw that exception but with my solution it wont. – Ricardo Duran Jan 4 at 16:50
  • 3
    The whole point of "null" objects is that you can call your methods on them as if they were real. If you have to check every one if it's "not null" (aka empty) before usage, then it kinda misses the point of that design pattern, because you could have checked for null just the same. – nvoigt Jan 4 at 16:54
  • @nvoigt And using nulls for empty means if you make a mistake it goes boom right away. Using workarounds for null makes it much easier to produce a wrong result rather than going boom. In almost all cases it's better to go boom. (Maybe you trap & log the error, but if something's gone wrong you shouldn't be continuing.) – Loren Pechtel Jan 22 at 4:37
  • @LorenPechtel: Not having it blow up with null reference exceptions is nice because you don't need to check before you do anything with the (hopefully not null) object. But prepending an empty check is just as much effort as prepending a null check. The empty approach is still functionally equivalent, but significantly more complex to implement and therefore loses out to the null approach when the actual benefits aren't being leveraged. – Flater Jan 22 at 11:24
3

You indeed can't inherit a static function. But if you need that static function to create an empty object, then you can also require that the derived classes implement a particular constructor.

The base class could then be, for example

public abstract class DataModel<TModel>: BaseEntity, IEmptiable<TModel> where TModel : new()
{
    protected struct EmptyTag {};

    private bool _isEmpty = false;
    private static TModel _empty = null;

    public virtual static TModel Empty {
        get {
            if (_empty == null) {
                _empty = new TModel(EmptyTag());
                _empty._isEmpty = true;
            }
            return _empty;
        }
    }

    public virtual bool IsEmpty() => _isEmpty;
}

public class User: DataModel<User> {
   public User(DataModel<User>.EmptyTag){
      this.Username = "*";
      this.Email = "na@empty.com";
   }

   public User(string username,string email){
      this.Username = username;
      this.Email = email;
   }

   //...
}

User aUser = User.Empty;
if (aUser.isEmpty())
  // ...
|improve this answer|||||
0

You should look into the default operator. Returning

default(T)

would isolate other classes or generics from knowing specifics about what that would be.

Or pass the Empty T into DataModel so it doesn't have to have this knowledge and just compare against it.

|improve this answer|||||
0

You might use Activator (if the performance hit is acceptable) or ORM approach, e.g. - use Activator, yet cache the result; - expect a particular constructor, throw if not present, cache after first found; - expect a static private field of particular type that holds (inter alia) constructor method, throw if not present, cache after first found etc.

All the ORM methods i found uses reflection to some extent, yet it's not a problem with caching of an uninitialized instance.

At the moment I'm in deep religious confusion factory vs repository. So not sure whether you are heading the right direction.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.