Mat*_*ero 3 c# design-patterns nullpointerexception optional nullreferenceexception
因此,众所周知,臭名昭着NullReferenceException是软件产品中最常见的例外.我一直在阅读一些文章,并发现自己采用了可选方法.
它的目标是围绕可以为空的值创建某种封装
public sealed class Optional<T> where T : class {
private T value;
private Optional(T value) {
this.value = value;
}
//Used to create an empty container
public static Optional<T> Empty() {
return new Optional(null);
}
//Used to create a container with a non-null value
public static Optional<T> For(T value) {
return new Optional(value);
}
//Used to check if the container holds a non-null value
public bool IsPresent {
get { return value != null; }
}
//Retrieves the non-null value
public T Value {
get { return value; }
}
}
Run Code Online (Sandbox Code Playgroud)
之后,可以像这样返回现在的可选值:
public Optional<ICustomer> FindCustomerByName(string name)
{
ICustomer customer = null;
// Code to find the customer in database
if(customer != null) {
return Optional.Of(customer);
} else {
return Optional.Empty();
}
}
Run Code Online (Sandbox Code Playgroud)
并像这样处理:
Optional<ICustomer> optionalCustomer = repository.FindCustomerByName("Matt");
if(optionalCustomer.IsPresent) {
ICustomer foundCustomer = optionalCustomer.Value;
Console.WriteLine("Customer found: " + customer.ToString());
} else {
Console.WriteLine("Customer not found");
}
Run Code Online (Sandbox Code Playgroud)
我没有看到任何改进,只是改变了复杂性.程序员必须记住要检查一个值IsPresent,就像他必须记住检查是否一样value != null.
如果他忘了,他会得到NullReferenceException两种方法.
我错过了什么?Optional模式提供了什么样的优点(如果有的话)Nullable<T>和null合并运算符?
如果你想Option为Nullable不同的名字,那么你是完全正确的- Option仅仅是Nullable引用类型.
Option如果您将其视为monad或包含一个或零值的专用集合,则该模式更有意义.
Option 作为一个集合考虑一个简单的foreach循环,其列表不能是null:
public void DoWork<T>(List<T> someList) {
foreach (var el in someList) {
Console.WriteLine(el);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您将空列表传递给DoWork,则没有任何反应:
DoWork(new List<int>());
Run Code Online (Sandbox Code Playgroud)
如果您传递包含一个或多个元素的列表,则会发生以下情况:
DoWork(new List<int>(1));
// 1
Run Code Online (Sandbox Code Playgroud)
让我们将空列表None替换为列表,并在其中包含一个条目Some:
var None = new List<int>();
var Some = new List(1);
Run Code Online (Sandbox Code Playgroud)
我们可以将这些变量传递给DoWork我们,并获得与以前相同的行为:
DoWork(None);
DoWork(Some);
// 1
Run Code Online (Sandbox Code Playgroud)
当然,我们也可以使用LINQ扩展方法:
Some.Where(x => x > 0).Select(x => x * 2);
// List(2)
// Some -> Transform Function(s) -> another Some
None.Where(x => x > 0).Select(x => x * 2);
// List()
// None -> None
Some.Where(x => x > 100).Select(x => x * 2);
// List() aka None
// Some -> A Transform that eliminates the element -> None
Run Code Online (Sandbox Code Playgroud)
有趣的旁注:LINQ是monadic.
通过将我们想要的值包装在列表中,如果我们实际上有一个值,我们突然只能对该值应用操作!
Optional考虑到这一点,让我们添加一些方法Optional让我们使用它就好像它是一个集合(或者,我们可以使它成为一个专门的版本IEnumerable只允许一个条目):
// map makes it easy to work with pure functions
public Optional<TOut> Map<TIn, TOut>(Func<TIn, TOut> f) where TIn : T {
return IsPresent ? Optional.For(f(value)) : Empty();
}
// foreach is for side-effects
public Optional<T> Foreach(Action<T> f) {
if (IsPresent) f(value);
return this;
}
// getOrElse for defaults
public T GetOrElse(Func<T> f) {
return IsPresent ? value : f();
}
public T GetOrElse(T defaultValue) { return IsPresent ? value: defaultValue; }
// orElse for taking actions when dealing with `None`
public void OrElse(Action<T> f) { if (!IsPresent) f(); }
Run Code Online (Sandbox Code Playgroud)
然后你的代码变成:
Optional<ICustomer> optionalCustomer = repository.FindCustomerByName("Matt");
optionalCustomer
.Foreach(customer =>
Console.WriteLine("Customer found: " + customer.ToString()))
.OrElse(() => Console.WriteLine("Customer not found"));
Run Code Online (Sandbox Code Playgroud)
那里没有多少积蓄,对吗?还有两个匿名函数 - 那我们为什么要这样做呢?因为,就像LINQ一样,它使我们能够建立一个只有在我们需要输入时才执行的行为链.例如:
optionalCustomer
.Map(predictCustomerBehavior)
.Map(chooseIncentiveBasedOnPredictedBehavior)
.Foreach(scheduleIncentiveMessage);
Run Code Online (Sandbox Code Playgroud)
所有这些行动(predictCustomerBehavior,chooseIncentiveBasedOnPredictedBehavior,scheduleIncentiveMessage)是昂贵的-但如果我们有一个客户开始与他们才会发生!
虽然它变得更好 - 经过一些研究,我们意识到我们无法始终预测客户行为.所以我们更改签名predictCustomerBehavior以返回Optional<CustomerBehaviorPrediction>并将Map链中的第二个调用更改为FlatMap:
optionalCustomer
.FlatMap(predictCustomerBehavior)
.Map(chooseIncentiveBasedOnPredictedBehavior)
.Foreach(scheduleIncentiveMessage);
Run Code Online (Sandbox Code Playgroud)
其定义为:
public Optional<TOut> FlatMap<TIn, TOut>(Func<TIn, Optional<TOut>> f) where TIn : T {
var Optional<Optional<TOut>> result = Map(f)
return result.IsPresent ? result.value : Empty();
}
Run Code Online (Sandbox Code Playgroud)
这开始看起来很像LINQ(例如FlatMap- > Flatten).
为了获得更多的实用性,Optional我们应该真正实现它IEnumerable.另外,我们可以利用多态性并创建两个子类型Optional,Some并None表示完整列表和空列表情况.然后我们的方法可以放弃IsPresent检查,使它们更容易阅读.
LINQ对昂贵操作的优势显而易见:
someList
.Where(cheapOp1)
.SkipWhile(cheapOp2)
.GroupBy(expensiveOp)
.Select(expensiveProjection);
Run Code Online (Sandbox Code Playgroud)
Optional,当被视为一个或零值的集合时,提供了类似的好处(并且没有理由它无法实现,IEnumerable因此LINQ方法也适用于它):
someOptional
.FlatMap(expensiveOp1)
.Filter(expensiveOp2)
.GetOrElse(generateDefaultValue);
Run Code Online (Sandbox Code Playgroud)