【问题标题】:Using Expression to call a property and object and determine if the object is null or not使用表达式调用属性和对象并确定对象是否为空
【发布时间】:2012-04-10 12:22:16
【问题描述】:

我希望能够调用可能为 null 但不必在调用时明确检查它们是否为 null 的对象的属性。

像这样:

var something = someObjectThatMightBeNull.Property;

我的想法是创建一个接受表达式的方法,如下所示:

var something = GetValueSafe(() => someObjectThatMightBeNull.Property);

TResult? GetValueSafe<TResult>(Expression<Func<TResult>> expression) 
    where TResult : struct
{
    // what must I do?
}

我需要做的是检查表达式并确定someObjectThatMightBeNull 是否为空。我该怎么做?

如果有任何更聪明的懒惰方式,我也会很感激。

谢谢!

【问题讨论】:

    标签: c# lambda expression


    【解决方案1】:

    虽然很复杂,但也可以做到,不用离开“表达式之地”:

    // Get the initial property expression from the left 
    // side of the initial lambda. (someObjectThatMightBeNull.Property)
    var propertyCall = (MemberExpression)expression.Body;
    
    // Next, remove the property, by calling the Expression 
    // property from the MemberExpression (someObjectThatMightBeNull)
    var initialObjectExpression = propertyCall.Expression;
    
    // Next, create a null constant expression, which will 
    // be used to compare against the initialObjectExpression (null)
    var nullExpression = Expression.Constant(null, initialObjectExpression.Type);
    
    // Next, create an expression comparing the two: 
    // (someObjectThatMightBeNull == null)
    var equalityCheck = Expression.Equal(initialObjectExpression, nullExpression);
    
    // Next, create a lambda expression, so the equalityCheck 
    // can actually be called ( () => someObjectThatMightBeNull == null )
    var newLambda = Expression.Lambda<Func<bool>>(equalityCheck, null);
    
    // Compile the expression. 
    var function = newLambda.Compile();
    
    // Run the compiled delegate. 
    var isNull = function();
    

    话虽如此,正如 Andras Zoltan 在 cmets 中雄辩地指出的那样:“仅仅因为你可以并不意味着你应该这样做。”确保你有充分的理由这样做。如果有更好的方法,那就这样做吧。 Andras 有一个很好的解决方法。

    【讨论】:

    • 仅仅因为你可以并不意味着你应该
    • 大声笑 - 在某些情况下,我希望有人会对我这么说!
    【解决方案2】:

    您所说的称为 null 安全解除引用 - 这个 SO 特别提出了这个问题:C# if-null-then-null expression

    表达式并不是真正的答案(请参阅下文以澄清我做出该声明的原因)。不过,这种扩展方法可能是:

    public static TResult? GetValueSafe<TInstance, TResult>(this TInstance instance,
      Func<TInstance, TResult> accessor)
      where TInstance : class
      where TResult : struct
    {  
       return instance != null ? (TResult?)accessor(instance) : (TResult?)null;
    }
    

    现在你可以这样做了:

    MyObject o = null;
    int? i = o.GetValueSafe(obj => obj.SomeIntProperty);
    
    Assert.IsNull(i);    
    

    显然,当属性是结构时,这是最有用的;你可以减少到任何类型,只使用default(TResult) - 但是你会得到0 用于整数、双精度等:

    public static TResult GetValueSafe<TInstance, TResult>(this TInstance instance,
      Func<TInstance, TResult> accessor, TResult def = default(TResult))
      where TInstance : class
    {  
       return instance != null ? accessor(instance) : def;
    }
    

    第二个版本特别有用,因为它适用于任何TResult。我已经扩展了一个可选参数以允许调用者提供默认值,例如(使用之前代码中的o):

    int i = o.GetValueSafe(obj => obj.SomeIntProperty); //yields 0
    i = o.GetValueSafe(obj => obj.SomeIntProperty, -1); //yields -1
    
    //while this yields string.Empty instead of null
    string s = o.GetValueSafe(obj => obj.SomeStringProperty, string.Empty);
    

    编辑 - 回应大卫的评论

    David 认为我的回答是错误的,因为它没有提供基于表达式的解决方案,而这正是我们所要求的。我的观点是,任何真正正确且确实负责任的 SO 答案都应始终尝试为提出问题的人寻求更简单的解决方案(如果存在)。我相信人们普遍认为,在我们的日常职业生活中,应该避免对原本简单的问题采取过于复杂的解决方案。 SO 之所以如此受欢迎,是因为它的社区行为方式相同。

    David 还对我的“它们不是解决方案”这一不合理的说法提出了质疑 - 所以我现在将对此进行扩展,并说明为什么基于表达式的解决方案在很大程度上毫无意义,除非在极少数情况下OP 实际上并没有要求(顺便说一句,大卫的回答也没有涵盖)。

    具有讽刺意味的是,它本身可能使这个答案变得不必要地复杂:) 如果您实际上并不关心为什么表达式不是最佳途径,那么您可以放心地从这里忽略

    虽然说你可以用表达式解决这个问题是正确的,但对于问题中列出的示例来说,根本没有理由使用它们 - 它使最终相当复杂的事情变得过于复杂简单的问题;并且在运行时编译表达式的开销(然后将其丢弃,除非您放入缓存,除非您发出诸如调用站点之类的东西,如 DLR 使用的东西,否则很难正确处理)与解决方案相比我在这里介绍。

    任何解决方案的最终动机都是尽量减少调用者所需的工作,但同时您还需要将表达式分析器要做的工作保持在最低限度,否则如果没有大量工作,解决方案将变得几乎无法解决。为了说明我的观点 - 让我们看一下我们可以使用带有表达式的静态方法实现的最简单的方法,给定我们的对象o

    var i = GetValueSafe(obj => obj.SomeIntProperty);
    

    呃-哦,那个表达式实际上并没有做任何事情 - 因为它没有 传递 o 给它 - 表达式本身对我们没有用,因为我们需要实际的 o 的引用可能是null。所以 - 第一个解决方案自然是明确地传递引用:

    var i = GetValueSafe(o, obj => obj.SomeIntProperty);
    

    (注意——也可以写成扩展方法)

    因此,静态方法的工作是获取第一个参数并在调用它时将其传递给编译后的表达式。这也有助于识别要寻找其属性的表达式的类型。然而,它也完全否定了首先使用表达式的理由;因为方法本身可以立即决定是否访问该属性 - 因为它具有对可能是 null 的对象的引用。因此,在这种情况下,像我的扩展方法一样,简单地传递引用和访问器 delegate(而不是表达式)会更容易、更简单且更快

    正如我所提到的,有一种方法可以绕过必须传递实例,那就是执行以下操作之一:

    var i = GetValueSafe(obj => o.SomeIntProperty);
    

    或者

    var i = GetValueSafe(() => o.SomeIntProperty);
    

    我们正在打折扩展方法版本 - 因为这样我们获得了传递给方法的引用,并且一旦我们获得引用,我们就可以取消表达式,正如我最后一点所证明的那样。 em>

    这里我们依赖调用者来理解他们必须在表达式,在成员 read 的左侧,以便我们实际上可以从中获取具体值,以便进行 null 检查。

    首先,这不是表达式参数的自然使用,所以我相信您的调用者可能会感到困惑。还有另一个问题,如果您打算经常使用它,我认为这将是一个杀手 - 您无法缓存这些表达式,因为每次您想要回避其“null-ness”的实例都被烘焙到表达式中即通过。这意味着您总是必须为每次调用重新编译表达式;这将是真的缓慢。如果您在表达式中对实例进行参数化,则可以将其缓存 - 但最终您会得到我们的第一个解决方案,该解决方案需要传递实例;我已经再次证明了我们可以使用委托!

    相对容易——使用ExpressionVisitor 类——编写可以将所有属性/字段读取(以及与此相关的方法调用)转换为您想要的“安全”调用的东西。但是,除非您打算安全阅读以下内容,否则我看不出这样做有什么好处:a.b.c.d。但是随后将值类型扩充为它们自身的可空版本将在表达式树重写中给您带来一些麻烦,我可以告诉您;留下一个几乎没有人会理解的解决方案:)

    【讨论】:

    • 这很不错!但是,它会拆分表达式,这意味着它将对象与属性拆分。但我认为它会做。谢谢!
    • 不返回结构的默认值有点重要。我宁愿为他们返回 null,但这很容易做到。
    • 只是好奇,为什么这里的“表达式不是真正的答案”?有什么真正的原因吗?
    • @DavidMorton,坚持使用表达式并没有好处,除了像a.b.c这样的链式表达式(无论如何都很难正确实现,例如如果任何左侧表达式为空,则使用什么规则进行短路?);该表达式将对我的非动态代码将执行的实例执行相同的空检查,但速度会明显变慢 - 所以除了“聪明”之外没有任何意义。
    • 是的,就是我。我反对它不是因为它鼓励做一些可能更好也可能不会更好的事情,而是基于它没有直接回答这个问题,而是提供了一种解决方法。在 StackOverflow 页面上,无论是否有“充分的理由”去做 OP 要求的事情,都没有回答 OP 的问题重要。 “哪个更好,这个或那个”,确实是一个完全不同的问题。访问此页面的人可能会寻找答案,而不是解决方法。
    猜你喜欢
    • 2014-10-12
    • 1970-01-01
    • 2013-07-30
    • 2014-08-22
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 2020-08-31
    • 2022-01-23
    相关资源
    最近更新 更多