扩展C#Coalesce运算符

lak*_*tak 14 c# extension-methods

在我解释我想做什么之前,如果你看下面的代码,你会明白它应该做什么吗?(更新 - 见下文)

Console.WriteLine(
  Coalesce.UntilNull(getSomeFoo(), f => f.Value) ?? "default value");
Run Code Online (Sandbox Code Playgroud)

C#已经有一个null-coalescing运算符,它在简单对象上运行得很好,但如果你需要访问该对象的一个​​成员则无效.

例如

Console.WriteLine(getSomeString()??"default");
Run Code Online (Sandbox Code Playgroud)

效果很好,但它不会帮助你:

public class Foo
{
  public Foo(string value) { Value=value; }
  public string Value { get; private set; }
}

// this will obviously fail if null was returned
Console.WriteLine(getSomeFoo().Value??"default"); 

// this was the intention
Foo foo=getSomeFoo();
Console.WriteLine(foo!=null?foo.Value:"default");
Run Code Online (Sandbox Code Playgroud)

由于这是我经常遇到的事情,我想到使用扩展方法(旧版本):

public static class Extension
{
  public static TResult Coalesce<T, TResult>(this T obj, Func<T, TResult> func, TResult defaultValue)
  {
    if (obj!=null) return func(obj);
    else return defaultValue;
  }

  public static TResult Coalesce<T, TResult>(this T obj, Func<T, TResult> func, Func<TResult> defaultFunc)
  {
    if (obj!=null) return func(obj);
    else return defaultFunc();
  }
}
Run Code Online (Sandbox Code Playgroud)

这让我写:

Console.WriteLine(getSomeFoo().Coalesce(f => f.Value, "default value"));
Run Code Online (Sandbox Code Playgroud)

那么你会认为这段代码是可读的吗?Coalesce是个好名字吗?

编辑1:删除Marc建议的括号

更新

我真的很喜欢lassevk的建议和Groo的反馈.所以我添加了重载并没有将它作为扩展方法实现.我还认为defaultValue是多余的,因为你可以使用现有的?操作员.

这是修订后的课程:

public static class Coalesce
{
  public static TResult UntilNull<T, TResult>(T obj, Func<T, TResult> func) where TResult : class
  {
    if (obj!=null) return func(obj);
    else return null;
  }

  public static TResult UntilNull<T1, T2, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, TResult> func2) where TResult : class
  {
    if (obj!=null) return UntilNull(func1(obj), func2);
    else return null;
  }

  public static TResult UntilNull<T1, T2, T3, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, TResult> func3) where TResult : class
  {
    if (obj!=null) return UntilNull(func1(obj), func2, func3);
    else return null;
  }

  public static TResult UntilNull<T1, T2, T3, T4, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3, Func<T4, TResult> func4) where TResult : class
  {
    if (obj!=null) return UntilNull(func1(obj), func2, func3, func4);
    else return null;
  }
}
Run Code Online (Sandbox Code Playgroud)

样品用法:

Console.WriteLine(
  Coalesce.UntilNull(getSomeFoo(), f => f.Value) ?? "default value");
Run Code Online (Sandbox Code Playgroud)

另一个样本:

public class Bar
{
  public Bar Child { get; set; }
  public Foo Foo { get; set; }
}

Bar bar=new Bar { Child=new Bar { Foo=new Foo("value") } };

// prints "value":
Console.WriteLine(
  Coalesce.UntilNull(bar, b => b.Child, b => b.Foo, f => f.Value) ?? "null");

// prints "null":
Console.WriteLine(
  Coalesce.UntilNull(bar, b => b.Foo, f => f.Value) ?? "null");
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 11

是的,我会理解的.是的,合并是个好名字.是的,如果C#有一个像Groovy和其他一些语言的null安全解除引用运算符会更好:)

更新

C#6有这样一个运算符 - 空条件运算符,?.例如:

var street = customer?.PrimaryAddress?.Street;
Run Code Online (Sandbox Code Playgroud)

如果您仍需要默认值,请将其与原始的null-coalescing运算符一起使用.例如:

var street = customer?.PrimaryAddress?.Street ?? "(no address given)";
Run Code Online (Sandbox Code Playgroud)

或者,根据问题中的原始代码:

Console.WriteLine(getSomeFoo()?.Value ?? "default"); 
Run Code Online (Sandbox Code Playgroud)

当然注意到,只有当最终属性值可用但由于某种原因设置为null时才可以使用该默认值,这种方式提供默认值是有效的.

表达式的结果x?.ynullif x计算为null; 否则就是结果了x.y.哦,您也可以将它用于条件方法调用:

possiblyNull?.SomeMethod();
Run Code Online (Sandbox Code Playgroud)


Mar*_*ell 7

它已经让我感到困惑......通常,你会想到合并它的 - 我想象的是第一个非null (f) => f.Value,并且"default value"将被返回,而不是这种情况(null测试是在原始实例上).

注意没有括号会更清楚吗?

f => f.Value
Run Code Online (Sandbox Code Playgroud)

你实际上做的是类似的Select- 所以 SafeSelect一个好名字,IMO(但可能不完全是......).

或者甚至简单地说Dereference,只要参数名称(等)清楚地知道第二个arg是什么.


lak*_*tak 0

六年后,空条件运算符出现了:

\n\n
\n

有时,代码往往会被空检查淹没。null 条件运算符仅允许您在接收者不为 null 时访问成员和元素,否则提供 null 结果:

\n\n
int? length = customers?.Length; // null if customers is null Customer\nfirst = customers?[0];  // null if customers is null\n
Run Code Online (Sandbox Code Playgroud)\n\n

null 条件运算符可以方便地与\n null 合并运算符 ?? 一起使用:

\n\n
int length = customers?.Length ?? 0; // 0 if customers is null\n
Run Code Online (Sandbox Code Playgroud)\n\n

null 条件运算符表现出短路行为,其中仅当原始接收者不为 null 时,才会执行紧随其后的成员访问、元素访问和调用链:

\n\n
int? first = customers?[0].Orders.Count();\n
Run Code Online (Sandbox Code Playgroud)\n\n

这个例子本质上等价于:

\n\n
int? first = (customers != null) ? customers[0].Orders.Count() : null;\n
Run Code Online (Sandbox Code Playgroud)\n\n

只不过客户只评估一次。除非客户具有非空值,否则不会执行紧随 ?\n 的成员\n 访问、元素访问和调用。

\n\n

当然,空条件运算符本身可以链接起来,以防需要在链中多次检查空值:

\n\n
int? first = customers?[0].Orders?.Count();\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,调用(带括号的参数列表)不能紧跟在 ? 后面。运算符 \xe2\x80\x93 会导致太多\n 语法歧义。因此,仅当存在委托时才调用委托的直接方法是行不通的。但是,您可以通过委托上的 Invoke 方法来完成此操作:

\n\n
if (predicate?.Invoke(e) ?? false) { \xe2\x80\xa6 }\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们预计此模式的一个非常常见的用途是触发事件:

\n\n
PropertyChanged?.Invoke(this, args);\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是在触发事件之前检查 null 的简单且线程安全的方法。它\xe2\x80\x99s 线程安全的原因是该功能\n 仅对左侧求值一次,并将其保存在临时\n 变量中。

\n
\n