Общий шаблон нулевого объекта в С#

Мне интересно, существует ли какой-либо подход для реализации шаблона нулевого объекта в С#. Общий нулевой объект является подклассом всех ссылочных типов, как и Nothing в Scala. Кажется,

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

Но он не может скомпилировать, и я понятия не имею , как реализовать методы T для обеспечения поведения по умолчанию или исключения исключений. Вот несколько соображений:

  • Использовать отражение?
  • Использовать дерево выражений при создании Nothing? Возможно, это похоже на Moq. И еще вопрос: можно ли использовать mock framework/library в кодах продуктов?
  • Использовать динамические типы?

Я ЗНАЮ, возможно, я должен реализовать конкретный нулевой объект для определенного типа. Мне просто интересно узнать, есть ли какие-либо решения.

Любое предложение? Спасибо.

5 ответов

С помощью дженериков вы не можете определить наследование от T. Если вы намерены использовать if(x is Nothing<foo>)</foo>, то это просто не сработает. Не в последнюю очередь, вам нужно будет подумать об абстрактных типах, опечатанных типах и конструкторах, не относящихся к умолчанию. Однако вы можете сделать что-то вроде:

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

Однако IMO, которая не выполняет большинство ключевых функций нулевого объекта; в частности, вы могли бы легко в конечном итоге с кем-то сделать:

Nothing<foo>.Instance.SomeProp = "abc";
</foo>

(возможно, случайно, после прохода объекта 3 уровня вниз)

Честно говоря, я думаю, вам стоит просто проверить null.


Как насчет этого?

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


Так как есть запечатанные классы, вы не можете сделать такое наследование в родовом случае. Ожидается, что многие классы не будут получены, поэтому может быть неплохо, если бы это сработало.

Использование неявного оператора, предложенного @abatishchev, звучит как возможный подход.


Я использую что-то подобное в своих проектах:

public interface IOptional<t> : IEnumerable<t> { }
public interface IMandatory<t> : IEnumerable<t> { }
</t></t></t></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();
 }
}
</t></t></t></t></t></t></t>

Реализация классов Just и Some

Примечание: реализация этих классов очень похожа, но имеет одно отличие. Класс Просто получен из интерфейса 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();
 }
}
</t></t></toutput></toutput></toutput></tinput,></tinput></tinput,></toutput>

Некоторые расширения для практичности

Примечание: метод расширения Сопоставьте две функции и верните 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()
 );
</int></int>


Как насчет уже существующей реализации Nullable в .NET Framework? http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx

licensed under cc by-sa 3.0 with attribution.