深空检查,有没有更好的办法?深空检查,有没有更好的办法?(Deep null checking, i

2019-05-08 23:18发布

注意:这个问题是在引进之前问的.? 操作者在C#6 / Visual Studio中2015 。

我们都在那里,我们有一些深物业类似cake.frosting.berries.loader,我们需要检查它是否为空,所以没有例外。 做的方式是使用短路if语句

if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...

这是不完全高雅,并有也许应该检查整个链条,看看它是否对一个空的变量/属性来了一个更简单的方法。

是否有可能使用一些扩展方法或者这将是一个语言特性,还是仅仅是一个坏主意?

Answer 1:

我们曾考虑增加一个新的操作“?”。 到了你想要的语义的语言。 (它已经现在增加;见下文)也就是说,你会说

cake?.frosting?.berries?.loader

而编译器会产生为您的所有短路检查。

它没有为C#4条也许对于语言的一个假想的未来版本。

更新(2014):?. 运营商正在计划在未来罗斯林编译器版本。 需要注意的是仍然有在运营商的确切语法和语义分析的一些争论。

更新(2015年7月):的Visual Studio 2015年已被释放,并附带支持一个C#编译器空条件运算符?.?[]



Answer 2:

我被这个问题启发,试图找出如何这种深空检查的可以使用表达式树更容易/漂亮的语法来完成。 虽然我做的答案,说明这可能是一个糟糕的设计,如果你经常需要访问的实例深层次结构中的同意,我也确实认为,在某些情况下,如数据表现,它可以是非常有用的。

所以,我创建了一个扩展方法,这将允许你这样写:

var berries = cake.IfNotNull(c => c.Frosting.Berries);

这将返回浆果如果没有表达的部分为空。 如果遇到空,则返回null。 还有一些注意事项,虽然在目前的版本中,它只会用简单的成员访问工作,它只能在.NET Framework 4中,因为它使用了MemberExpression.Update方法,它是在V4新。 这是用于延长IfNotNull方法的代码:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace dr.IfNotNullOperator.PoC
{
    public static class ObjectExtensions
    {
        public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (ReferenceEquals(arg, null))
                return default(TResult);

            var stack = new Stack<MemberExpression>();
            var expr = expression.Body as MemberExpression;
            while(expr != null)
            {
                stack.Push(expr);
                expr = expr.Expression as MemberExpression;
            } 

            if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
                throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
                                                             expression));

            object a = arg;
            while(stack.Count > 0)
            {
                expr = stack.Pop();
                var p = expr.Expression as ParameterExpression;
                if (p == null)
                {
                    p = Expression.Parameter(a.GetType(), "x");
                    expr = expr.Update(p);
                }
                var lambda = Expression.Lambda(expr, p);
                Delegate t = lambda.Compile();                
                a = t.DynamicInvoke(a);
                if (ReferenceEquals(a, null))
                    return default(TResult);
            }

            return (TResult)a;            
        }
    }
}

它通过检查表达式树代表你的表达,并评估零件一个接一个; 每次检查的结果是不为空。

我相信这可以延长,使其他表达式比MemberExpression支持。 认为这是证明了概念代码,请记住,会有性能上的损失通过使用它(这可能没有关系在许多情况下,却在紧密循环:-)不使用它)



Answer 3:

我发现这个扩展成为深嵌套场景非常有用。

public static R Coal<T, R>(this T obj, Func<T, R> f)
    where T : class
{
    return obj != null ? f(obj) : default(R);
}

这是一个想法,我在C#和T-SQL的空合并运算符derrived。 这种做法的好处是,返回类型始终是内部属性的返回类型。

这样,你可以这样做:

var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);

...或上述的微小变化:

var berries = cake.Coal(x => x.frosting, x => x.berries);

这不是我所知道的最好的语法,但它确实工作。



Answer 4:

除了违反迪米特法则,为迈赫达德Afshari已经指出的那样,在我看来,你需要“深空检查”的决策逻辑。

这是最常见的,当你想替换默认值空物体的情况。 在这种情况下,你应该考虑实施空对象模式 。 它作为一个独立的在一个真正的对象,提供默认值,“不采取行动”的方法。



Answer 5:

更新:使用Visual Studio 2015年开始,C#编译器(语言版本6)现在承认?. 运营商,这使“深空检查”变得轻而易举。 见这个答案的详细信息。

除了重新设计你的代码,像这样删除的答案建议,另一个(虽然可怕)选择是使用try…catch块,看是否有NullReferenceException是深物业查找期间发生的某个时候。

try
{
    var x = cake.frosting.berries.loader;
    ...
}
catch (NullReferenceException ex)
{
    // either one of cake, frosting, or berries was null
    ...
}

我个人不会,原因如下做到这一点:

  • 它并不好看。
  • 它使用异常处理,应该针对您所期望正常的操作过程中,经常发生异常情况,而不是东西。
  • NullReferenceException S的关系可能永远不会被抓到明确。 (见这个问题 。)

那么,是否可以使用一些扩展方法,或者这将是一个语言功能,[...]

这几乎肯定会必须是一个语言功能(该功能可在C#6的形式.??[]运营商),除非C#已经有更复杂的延迟计算,或者除非你想使用反射(这可能也不是对性能和类型安全)的原因是一个好主意。

由于没有办法简单地传递cake.frosting.berries.loader的功能(它会进行评估,并抛出一个空引用除外),你就必须实现下列方式一般查找方法:这需要在对象和属性的名称来查询:

static object LookupProperty( object startingPoint, params string[] lookupChain )
{
    // 1. if 'startingPoint' is null, return null, or throw an exception.
    // 2. recursively look up one property/field after the other from 'lookupChain',
    //    using reflection.
    // 3. if one lookup is not possible, return null, or throw an exception.
    // 3. return the last property/field's value.
}

...

var x = LookupProperty( cake, "frosting", "berries", "loader" );

(注:代码编辑。)

你快看几个问题这样的做法。 首先,你没有得到任何类型的安全性和简单类型的属性值的可能拳。 其次,你可以返回null ,如果出现错误,你将在你的调用函数来检查这一点,或者你抛出一个异常,而你回到你开始的地方。 第三,它可能是缓慢的。 第四,它看起来比你开始用什么丑陋。

[...],还是仅仅是一个坏主意?

我想无论是留在:

if (cake != null && cake.frosting != null && ...) ...

或与迈赫达德Afshari上面的回答去。


PS:回来时,我写了这个答案,我显然没有考虑lambda函数表达式树; 例如参见@driis'答案在这个方向的解决方案。 它也是基于一种反映,因此可能无法很好地执行一个简单的解决方案( if (… != null & … != null) … ),但可以判断从点的视图语法更好。



Answer 6:

虽然driis的回答很有趣,我认为这是一个有点太贵了性能明智的。 而不是编译许多代表,我宁愿编译每个属性路径,它缓存一个拉姆达,然后重新调用它的许多类型。

下面NullCoalesce做到了这一点,它返回一个null检查一个新的lambda表达式和默认的情况下返回(TResult)任何路径为空。

例:

NullCoalesce((Process p) => p.StartInfo.FileName)

将返回一个表达式

(Process p) => (p != null && p.StartInfo != null ? p.StartInfo.FileName : default(string));

码:

    static void Main(string[] args)
    {
        var converted = NullCoalesce((MethodInfo p) => p.DeclaringType.Assembly.Evidence.Locked);
        var converted2 = NullCoalesce((string[] s) => s.Length);
    }

    private static Expression<Func<TSource, TResult>> NullCoalesce<TSource, TResult>(Expression<Func<TSource, TResult>> lambdaExpression)
    {
        var test = GetTest(lambdaExpression.Body);
        if (test != null)
        {
            return Expression.Lambda<Func<TSource, TResult>>(
                Expression.Condition(
                    test,
                    lambdaExpression.Body,
                    Expression.Default(
                        typeof(TResult)
                    )
                ),
                lambdaExpression.Parameters
            );
        }
        return lambdaExpression;
    }

    private static Expression GetTest(Expression expression)
    {
        Expression container;
        switch (expression.NodeType)
        {
            case ExpressionType.ArrayLength:
                container = ((UnaryExpression)expression).Operand;
                break;
            case ExpressionType.MemberAccess:
                if ((container = ((MemberExpression)expression).Expression) == null)
                {
                    return null;
                }
                break;
            default:
                return null;
        }
        var baseTest = GetTest(container);
        if (!container.Type.IsValueType)
        {
            var containerNotNull = Expression.NotEqual(
                container,
                Expression.Default(
                    container.Type
                )
            );
            return (baseTest == null ?
                containerNotNull :
                Expression.AndAlso(
                    baseTest,
                    containerNotNull
                )
            );
        }
        return baseTest;
    }


Answer 7:

一种选择是使用空对象彭定康,因此不是空当你没有一个蛋糕,你有一个返回NullFosting等。对不起,我不是很擅长解释这个,但是其他人都是NullCake,见

  • 的空对象彭定康使用例
  • 该wikipedai写上来就空对象彭定康


Answer 8:

我也经常希望能有简单的语法! 当你有方法返回值可能是零,因为那么你需要额外的变量,它变得特别难看(例如: cake.frosting.flavors.FirstOrDefault().loader

然而,这里就是我用一个相当不错的选择:创建一个空皆宜链的辅助方法。 我意识到,这是非常相似@约翰的回答以上(与Coal扩展方法),但我觉得它更简单,少打字。 这里是什么样子:

var loader = NullSafe.Chain(cake, c=>c.frosting, f=>f.berries, b=>b.loader);

这里的实现:

public static TResult Chain<TA,TB,TC,TResult>(TA a, Func<TA,TB> b, Func<TB,TC> c, Func<TC,TResult> r) 
where TA:class where TB:class where TC:class {
    if (a == null) return default(TResult);
    var B = b(a);
    if (B == null) return default(TResult);
    var C = c(B);
    if (C == null) return default(TResult);
    return r(C);
}

我还创建几个重载(具有2至6个参数),以及重载允许链条与一个数值类型或默认结束。 这工作对我很好!



Answer 9:

有可能CodePlex项目实现也许或IfNotNull使用lambda表达式深表达式C#

使用示例:

int? CityId= employee.Maybe(e=>e.Person.Address.City);

链接有人提出类似的问题, 如何在深lambda表达式检查空值?



Answer 10:

作为建议约翰Leidegren的答案 ,一个办法变通,这是使用扩展方法和代表。 使用它们可能是这个样子:

int? numberOfBerries = cake
    .NullOr(c => c.Frosting)
    .NullOr(f => f.Berries)
    .NullOr(b => b.Count());

因为你需要得到它的值类型,引用类型和空值类型工作的实施是凌乱。 你可以找到一个完整的实现Timwi的回答到什么是检查空值的正确方法? 。



Answer 11:

或者,你可以使用反射:)

反射功能:

public Object GetPropValue(String name, Object obj)
    {
        foreach (String part in name.Split('.'))
        {
            if (obj == null) { return null; }

            Type type = obj.GetType();
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }

            obj = info.GetValue(obj, null);
        }
        return obj;
    }

用法:

object test1 = GetPropValue("PropertyA.PropertyB.PropertyC",obj);

我的情况(在反​​射功能返回,而不是空DBNull.Value):

cmd.Parameters.AddWithValue("CustomerContactEmail", GetPropValue("AccountingCustomerParty.Party.Contact.ElectronicMail.Value", eInvoiceType));


Answer 12:

试试这个代码:

    /// <summary>
    /// check deep property
    /// </summary>
    /// <param name="obj">instance</param>
    /// <param name="property">deep property not include instance name example "A.B.C.D.E"</param>
    /// <returns>if null return true else return false</returns>
    public static bool IsNull(this object obj, string property)
    {
        if (string.IsNullOrEmpty(property) || string.IsNullOrEmpty(property.Trim())) throw new Exception("Parameter : property is empty");
        if (obj != null)
        {
            string[] deep = property.Split('.');
            object instance = obj;
            Type objType = instance.GetType();
            PropertyInfo propertyInfo;
            foreach (string p in deep)
            {
                propertyInfo = objType.GetProperty(p);
                if (propertyInfo == null) throw new Exception("No property : " + p);
                instance = propertyInfo.GetValue(instance, null);
                if (instance != null)
                    objType = instance.GetType();
                else
                    return true;
            }
            return false;
        }
        else
            return true;
    }


Answer 13:

我这个昨晚公布,然后一个朋友向我指出这个问题。 希望能帮助到你。 然后,您可以做这样的事情:

var color = Dis.OrDat<string>(() => cake.frosting.berries.color, "blue");


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DeepNullCoalescence
{
  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;
      }
    }
  }
}

阅读在这里充分的博客文章 。

同样的朋友也建议你看这个 。



Answer 14:

我微微由改性代码在这里 ,使之成为问问题的工作:

public static class GetValueOrDefaultExtension
{
    public static TResult GetValueOrDefault<TSource, TResult>(this TSource source, Func<TSource, TResult> selector)
    {
        try { return selector(source); }
        catch { return default(TResult); }
    }
}

是的,这可能不是由于try / catch语句的性能问题的最佳解决方案 ,但它的工作原理:>

用法:

var val = cake.GetValueOrDefault(x => x.frosting.berries.loader);


Answer 15:

你需要做到这一点,做到这一点:

用法

Color color = someOrder.ComplexGet(x => x.Customer.LastOrder.Product.Color);

要么

Color color = Complex.Get(() => someOrder.Customer.LastOrder.Product.Color);

辅助类实现

public static class Complex
{
    public static T1 ComplexGet<T1, T2>(this T2 root, Func<T2, T1> func)
    {
        return Get(() => func(root));
    }

    public static T Get<T>(Func<T> func)
    {
        try
        {
            return func();
        }
        catch (Exception)
        {
            return default(T);
        }
    }
}


Answer 16:

我喜欢的Objective-C所取的方法:

“在Objective-C语言需要另一种方法解决这个问题并不会调用上零的方法,而是返回nil所有这样的调用。”

if (cake.frosting.berries != null) 
{
    var str = cake.frosting.berries...;
}


文章来源: Deep null checking, is there a better way?
标签: c# null