为什么可空的显式转换LINQ运算符会在空值上抛出无效的格式异常?

时间:2011-01-28 08:36:20

标签: c# linq linq-to-sql

首先,我知道这个问题可以通过一个简单的回答回答,即空字符串不是空值。此外,我最近才通过另一个stackoverflow问题在今年早些时候发现了演员操作员,并且没有很多经验。即使如此,当与空合并运算符结合使用时,这些转换运算符并不完全那么简单的原因被称为处理LINQ表达式中缺少元素或属性等错误条件的优雅解决方案。我开始使用the approach described by ScottH in "Improving LINQ Code Smell..."和ScottGu "Null coalescing operator (and using it with LINQ)"作为一种以简洁和半优雅的方式防范无效/丢失数据的方法。从我可以收集的内容看来,这似乎是将所有转换重载放在LINQ类中的动机之一。

因此,在我看来,处理缺失值的用例与处理空值并不完全不同,在链接文章中,这种方法被认为是处理这种情况的好方法

情景:

int length = (int?)elem.Attribute("Length") ?? 0;

如果缺少@Length属性,则强制转换为空值并且运算符返回0;

如果@Length属性存在但是为空,则转换内部在int.tryparse上分支并抛出格式异常。当然,对于我的用法,我希望它不会,并且只是返回null,所以我可以继续在我已经有些复杂的LINQ代码中使用这种方法。

最终,我不能提出一个解决方案,但更多的是我有兴趣听到是否有一个我错过的明显方法,或者是否有人对错过的原因有很好的认识价值情景得到了解决,但空值情景却没有。

修改

似乎有一些关键点我应该尝试并强调:

  • 链接文章来自MS工作人员,我很钦佩并寻求指导。这些文章似乎提出(或至少提请注意)处理可选值的改进/替代方法

  • 在我的情况下,可选值有时以缺少元素或属性的形式出现,但的形式出现元素或价值

  • 所描述的方法对于缺失值起作用,但对于空值失败

  • 遵循所描述的方法的代码似乎是在防止缺少可选值,但实际上是脆弱的并且在特定情况下会中断。事实上,你仍然面临着与我有关的风险

  • 如果目标元素存在但是为空,则可以通过声明两个链接的示例都因运行时异常而失败来突出显示

  • 最后,似乎关键的一点是,当您签订合同(可能通过架构)时,所描述的方法很有效,可确保元素或属性永远为空但在如果该合同不存在,这种方法无效,需要替代方案

2 个答案:

答案 0 :(得分:3)

我同意这种行为;如果我没有一个“id”属性/元素,那么我很高兴将其视为null,但如果它存在但不解析,那么是不同的 - 一个空字符串适用于很少的类型。你必须手动完成,或者你可以在XAttribute等上添加扩展方法:

public static int? ParseInt32(this XAttribute attrib) {
    if(attrib != null && string.IsNullOrEmpty(attrib.Value)) return null;
    return (int?)attrib;
}

注意我在内部使用强制转换为虽然int 很简单,但.ParseDateTime等来说是一个不好的例子,在xml中使用不同的格式,转换在内部处理。

答案 1 :(得分:1)

就解决方法而言,我已经在下面构建了扩展方法。我没有想到Marc描述的方法用可以为空的int修复问题并且仍然使用??用于指定默认值的运算符。非常有创意,并允许X ?? Y模式继续使用。

我认为最后我可能更喜欢Marc的方法,因为它在我的赋值语句中保持一致的模式,并没有引入TryParseInt调用所具有的语法混淆,因为它看起来太像你解析-1或者默认的/ fallback值已提供。

    elem.Attribute("ID").TryParseInt(-1)

    /// <summary>
    /// Parses an attribute to extract an In32 value and falls back to the defaultValue should parsing fail
    /// </summary>
    /// <param name="defaultValue">The value to use should Int32.TryParse fail</param>
    /// <returns>The parsed or default integer value</returns>
    public static int TryParseInt(this XAttribute attr, int defaultValue)
    {
        int result;
        if (!Int32.TryParse((string)attr, out result))
        {
            // When parsing fails, use the fallback value
            result = defaultValue;
        }

        return result;
    }