如何拆分多个级别的成员访问

本文关键字:成员 访问 何拆分 拆分 | 更新日期: 2023-09-27 17:59:01

让我们假设我定义了这些伪类:

public class MyObject
{
    public InterestingFact InterestingFact { get; set; }
}
public class InterestingFact
{
    public Detail Detail { get; set; }
}
public class Detail
{
    public string Information { get; set; }
}

我还有一个MyObject:的实例

var obj = new MyObject() { InterestingFact = new InterestingFact() };

如果我想尝试访问Information属性,我可以这样做:

string info = null;
if (obj != null
    && obj.InterestingFact != null
    && obj.InterestingFact.Detail != null)
{
    info = obj.InterestingFact.Detail.Information;
}

为了这个讨论,忽略了德米特定律,每次我想访问我所拥有的物体深处的东西时,这并不是一件很有趣的事。当然,我可以创建一个扩展方法:

public static U NullOr<T, U>(this T target, Func<T, U> selector)
{
    if (EqualityComparer<T>.Default.Equals(target, default(T)))
    {
        return default(U);
    }
    return selector(target);
}

这使得选择数据变得更容易:

// Here I have a null of type string.
var info = obj.NullOr(o => o.InterestingFact).NullOr(f => f.Detail).NullOr(d => d.Information);

然而,这仍然有点冗长。理想情况下,我想做一些更像这样的事情:

// Here I should still have a null of type string.
var info = obj.NullOr(o => o.InterestingFact.Detail.Information);

我从未使用过Expression;如果我接受Expression<Func<T, U>>而不是NullOr中的Func<T, U>,在我看来,这是一个成员访问,而不是三个成员访问。有没有办法解决上述问题,或者这种提法遥不可及?

(当然,还有一个问题是,上一个公式认为,发送的表达式可能不仅仅是链式成员访问。)

如何拆分多个级别的成员访问

使用扩展方法实现所需结果的最简单方法如下。

public static class ExtensionMethods
{
    public static TR DefaultIfNull<T, TR>(this T source, Expression<Func<T, TR>> expr, TR defaultValue = default(TR))
    {
        TR result = defaultValue;
        try
        {
            result = expr.Compile().Invoke(source);
        }
        catch (NullReferenceException)
        {
            // DO NOTHING
        }
        return result;
    }
}

下面是上面的一些用法示例

var info1 = obj.DefaultIfNull(x => x.InterestingFact.ToString(), "Null1");
var info2 = obj.DefaultIfNull(x => x.InterestingFact.Detail.ToString(), "Null2");
var info3 = obj.DefaultIfNull(x => x.InterestingFact.Detail.Information);

请注意,这不是BEST解决方案,因为它不检查单个表达式节点是否为null,因此,如果树中的任何节点碰巧在内部生成NullReferenceException,则将返回默认值,而不是抛出异常。然而,对于一般和简单的使用来说,这可能是最佳解决方案(主要是因为它的简单性)。