这比Null Coalesce运算符更好吗?

时间:2011-08-28 02:43:13

标签: c# linq null expression-trees

我今天遇到了this博客文章。

我会总结一下。这位博客正在评论这段代码并说它很难看。

// var line1 = person.Address.Lines.Line1 ?? string.Empty;
// throws NullReferenceException: 
//    {"Object reference not set to an instance of an object."}

// The ugly alternative

var line1 = person.Address == null
    ? "n/a"
    : person.Address.Lines == null
    ? "n/a"
    : person.Address.Lines.Line1;
然后,博主继续编写一个类,允许您用新语法替换上面的代码。

var line2 = Dis.OrDat<string>(() => person.Address.Lines.Line2, "n/a");

Class Dis 的代码如下。

 public static class Dis
  {
    public static T OrDat<T>(Expression<Func<T>> expr, T dat)
    {
      try
      {
        var func = expr.Compile();
        var result = func.Invoke();
        return result ?? dat; //now we can coalesce
      }
      catch (NullReferenceException)
      {
        return dat;
      }
    }
  }

所以我的第一个问题是为什么包含??的原始代码必须用被诅咒的?:代码替换。

我的第二个问题是为什么在Null Coalesce上使用表达式树?我能想到它的唯一原因是因为它们正在使用延迟初始化,但是从帖子中看不清楚,也不清楚博客帖子中的测试代码。我也会在这里发布。

BTW,有谁知道如何创建固定大小的代码块窗口?或者这是一个非滚动的代码块?我没有在Meta博客中看到任何内容。

6 个答案:

答案 0 :(得分:6)

我认为在博客样本中没有任何理由使用表达式树,Func就足够了。

我的建议是使用Maybe * monad iplementation,而不是代码。见例。

public static class Maybe
{
    public static TResult With<T, TResult>(this T self, Func<T, TResult> func) where T : class
    {
        if (self != null)
            return func(self);
        return default(TResult);
    }

    public static TResult Return<T, TResult>(this T self, Func<T, TResult> func, TResult result) where T : class
    {
        if (self != null)
            return func(self);
        return result;
    }
}

您的代码变为:

var line2 = person
   .With(p => p.Address)
   .With(a => a.Lines)
   .Return(l => l.Line2, "n/a");

[*]这不是真正的monad,而是一个非常简化的版本。

答案 1 :(得分:4)

代码是博客更糟糕的。方法名称Dis.OrDat很难看,并没有描述该方法实际执行的操作。

使用Expression<T>是多余的,可能只是:

public static T OrDat<T>(Func<T> func, T dat)
{
  try
  {
    return func() ?? dat;
  }
  catch (NullReferenceException)
  {
    return dat;
  }
}

他马上一个接一个地调用CompileInvoke,所以他实际上并没有对表达式树做任何事情。按原样传入Func<T>将是相同的,没有编译Func的开销。

但更糟糕的是,代码使用exceptions for flow control,这总是很糟糕:person.Address属性似乎是可选的,因此它不是“例外”,因为它是null,所以不应该是异常不会被使用它的代码抛出。上面的catch无法区分person.Address == nullAddress属性getter的实现在内部被破坏导致NullReferenceException被抛出。它只是吞下它们。

所以,总的来说,我很乐意忽视博客文章。

答案 2 :(得分:3)

保护此代码:

var line1 = person.Address.Lines.Line1 ?? string.Empty;

抛出NullReferenceException

我只想使用:

var line1 = string.Empty;
if ((person.Address != null) && (person.Address.Lines != null))
   line1 = person.Address.Lines.Line1 ?? string.Empty;

而不是Bloggers这个或那个(Dis.OrDat),

答案 3 :(得分:2)

第一行代码(var line1 = person.Address.Lines.Line1 ?? string.Empty)的问题在于,如果person,Address或Lines为null,则会抛出错误。空合并运算符仅处理整个表达式的结果。

这是一个相当优雅的解决方案但是我想在我开始通过我的代码开始之前检查表达式树的性能是什么样的(但仅仅因为我在过去之前过度使用了反射,然后我知道了什么是狗它是)

答案 4 :(得分:2)

对于您的第一个问题,相关代码:

var line1 = person.Address.Lines.Line1 ?? string.Empty;

如果NullReferenceExceptionpersonAddressLines,则

会抛出null。使用三元if语句的替换代码可以防止这种情况。空合并运算符仅对Line1属性进行操作,因此无法保护表达式null的其余部分。

对于第二个问题,使用表达式树的原因可能是“简化”确保整个表达式可以被唤醒所需的代码。虽然代码可行,但我认为它引入了一层并非真正必要或需要的复杂性和开销。

答案 5 :(得分:2)

对于那些使用C#6.0(或更高版本)查看此条目的人,代码现在可以使用 Null Propagation ,并按如下方式编写:

var line1 = person?.Address?.Lines?.Line1 ?? "n/a";