【问题标题】:How to break apart multiple levels of member access如何拆分多个级别的成员访问
【发布时间】:2025-12-21 02:30:11
【问题描述】:

假设我定义了这些虚拟类:

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);

我从未与Expressions 合作过;如果我使用Expression&lt;Func&lt;T, U&gt;&gt; 而不是NullOr 中的Func&lt;T, U&gt;,在我看来它是一个成员访问权限而不是三个成员访问权限。有没有办法解决上述问题,还是这个公式遥不可及?

(当然,还有人担心最后一个公式,发送的表达式可能不仅仅是链式成员访问..)

【问题讨论】:

  • 这是 proposed 使用 ?. 运算符对 C# 的补充,但它还没有进入语言。 Mads 说他们正在认真考虑,所以有一天你可以写信obj?.InterestingFact?.Detail?.Information。在那之前,你做的方式是我知道的唯一方式。
  • @MosheKatz:我已经看到了,我希望我可以通过运算符重载来实现。
  • @MosheKatz:谢谢!我不知道我是怎么错过的。

标签: c# linq


【解决方案1】:

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

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 解决方案,因为它不会检查单个表达式节点是否为空,因此如果树中的任何节点恰好在内部生成 NullReferenceException,则默认值将被返回而不是抛出异常。但是,对于一般和简单的用法,这可能是最佳解决方案(主要是为了简单)。

【讨论】:

  • 我还应该注意,这里使用表达式树解析器并没有任何好处。您可以通过直接调用 Func 来进一步简化我上面的代码。