16

我想知道是否有任何方法可以在 C# 中实现通用空对象模式。通用空对象是所有引用类型的子类,就像Nothing在 Scala 中一样。这好像是

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

但它无法编译,我不知道如何实现T提供默认行为或抛出异常的方法。以下是一些想法:

  1. 使用反射?
  2. 创建时使用表达式树Nothing<T>?它可能看起来像起订量。另一个问题来了:在产品代码中使用mock framework/library可以吗?
  3. 使用动态类型?

我知道也许我应该为特定类型实现特定的空对象。我只是想知道是否有任何解决方案。

有什么建议吗?谢谢。

4

5 回答 5

11

使用泛型,您不能定义继承自T. 如果您的意图是使用if(x is Nothing<Foo>),那么这是行不通的。尤其是,您需要考虑抽象类型、密封类型和非默认构造函数。但是,您可以执行以下操作:

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

但是,IMO 无法满足空对象的大多数关键功能;特别是,您很容易最终有人在做:

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

(也许是偶然的,在将物体向下传递了 3 层之后)

坦率地说,我认为你应该只检查null.

于 2012-07-05T07:40:39.477 回答
8

这个怎么样?

public class Nothing<T> where T : class
{
     public static implicit operator T(Nothing<T> nothing)
     {
          // your logic here
     }
}
于 2012-07-05T07:39:42.880 回答
1

由于存在密封类,因此您不能在通用情况下进行此类继承。许多类预计不会派生自,因此它是否可以工作可能不是一个好主意。

使用@abatishchev 建议的隐式运算符听起来像是一种可能的方法。

于 2012-07-05T07:41:02.463 回答
1

我在我的项目中使用这样的东西:

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

从 IEnumerable 派生的两个接口,用于与 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();
    }
}

Just 和 Some 类的实现

注意:这个类的实现非常相似,但有一个区别。类 Just 派生自接口 IMandatory 并且只有一个构造函数,这保证了类 Just 的实例内部总是有一个值。

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();
    }
}

一些实用性的扩展

注意:扩展方法 Match 需要两个函数并返回 IMandatory,之后扩展方法 Fold 使用 .First() 而不做任何检查。

现在我们可以使用 LINQ 的全部功能并编写类似的代码(我的意思是 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()
        );
于 2017-06-27T20:56:49.923 回答
0

.NET Framework 中已经存在的 Nullable 实现怎么样? http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx

于 2012-07-05T07:39:45.780 回答