Generic null object pattern in C#

末鹿安然 提交于 2019-12-08 14:44:01

问题


I'm wondering if there is any approach to implement generic null object pattern in C#. The generic null object is the subclass of all the reference types, just like Nothing in Scala. It seems like

public class Nothing<T> : T where T : class

But it can't compile and I've no idea how to implement the methods of T to provide default behavior or throw an exception. Here are some thinkings:

  1. Use reflection?
  2. Use expression tree when creating Nothing<T>? It maybe looks like Moq. And another question comes: Is it OK to use mock framework/library in product codes?
  3. Use dynamic types?

I KNOW maybe I should implement particular null object for particular type. I'm just curious to know if there is any solution.

Any suggestion? Thanks.


回答1:


With generics, you can't define inheritance from T. If your intent is to use if(x is Nothing<Foo>), then that just isn't going to work. Not least, you'd need to think about abstract types, sealed types, and non-default constructors. However, you could do something like:

public class Nothing<T> where T : class, new()
{
    public static readonly T Instance = new T();
}

However, IMO that fails most of the key features of a null-object; in particular, you could easily end up with someone doing:

Nothing<Foo>.Instance.SomeProp = "abc";

(perhaps accidentally, after passing an object 3 levels down)

Frankly, I think you should just check for null.




回答2:


How about this?

public class Nothing<T> where T : class
{
     public static implicit operator T(Nothing<T> nothing)
     {
          // your logic here
     }
}



回答3:


Since there are sealed classes you can't make such inheritance in generic case. Many classes are not expected to be derived from, so it may not be good idea if it would work.

Using implicit operator as suggested by @abatishchev sounds like a possible approach.




回答4:


I use something like this in my projects:

public interface IOptional<T> : IEnumerable<T> { }
public interface IMandatory<T> : IEnumerable<T> { }

Two interface derived from IEnumerable for compatibility with LINQ

public class Some<T> : IOptional<T>
{
    private readonly IEnumerable<T> _element;
    public Some(T element)
        : this(new T[1] { element })
    {

    }
    public Some()
        : this(new T[0])
    {}
    private Some(T[] element)
    {
        _element = element;
    }
    public IEnumerator<T> GetEnumerator()
    {
        return _element.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class Just<T> : IMandatory<T>
{
    private readonly T _element;

    public Just(T element)
    {
        _element = element;
    }
    public IEnumerator<T> GetEnumerator()
    {
        yield return _element;
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Implementation of classes Just and Some

Notice: Implementation of this classes is very similar, but it has one diference. Class Just derived from interface IMandatory and has only one constructor, which guarantees that instance of class Just always has a value inside.

public static class LinqExtensions
{
    public static IMandatory<TOutput> Match<TInput, TOutput>(
        this IEnumerable<TInput> maybe,
        Func<TInput, TOutput> some, Func<TOutput> nothing)
    {
        if (maybe.Any())
        {
            return new Just<TOutput>(
                        some(
                            maybe.First()
                        )
                    );
        }
        else
        {
            return new Just<TOutput>(
                        nothing()
                    );
        }
    }
    public static T Fold<T>(this IMandatory<T> maybe)
    {
        return maybe.First();
    }
}

Some extensions for practicality

Notice: Extension method Match required two functions and return IMandatory, after this, extension method Fold use .First() without any check.

Now we can use full power of LINQ and write code similar this one (I mean monads .SelectMany())

var five = new Just<int>(5);
var @null = new Some<int>();

Console.WriteLine(
            five
                .SelectMany(f => @null.Select(n => f * n))
                .Match(
                    some: r => $"Result: {r}",
                    nothing: () => "Ups"
                )
                .Fold()
        );



回答5:


How about the already existing implementation of Nullable in the .NET Framework? http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx



来源:https://stackoverflow.com/questions/11339729/generic-null-object-pattern-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!