我在我的项目中使用这样的东西:
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()
);