任何人都可以解释.NET的TryGetValue的基本原理吗?

时间:2013-04-02 02:45:15

标签: .net collections

有人能告诉我为什么即使查找失败,.NET泛型集合上的TryGetValue函数也会设置out值吗?

在我看来,我们有TryGetValue的全部原因是我可以设置初始默认/失败值,并且如果查找实际上成功,则只有TryGetValue更改了该值。这是该函数的 Try 部分。如果我在查找失败时想要未定义或异常行为,我将不会使用* Try * GetValue。

这个函数的重点应该是它内部只需要一次查找,其中任何在容器外部写入的其他方法都必须进行两次查找才能首先确定该值是否存在以及其次要检索它(使用try-catch设置)。

如果我使用Dictionary作为示例,则查找对< 3,0>使用TryGetValue(3,& local)在本地返回0。在地图中查找不包含任何对< 3,x>返回0.空映射中的查找返回0.如果0是我想要存储的有效值,则太糟糕了。

这意味着如果0是可接受的返回值,我必须在每次失败时手动将值重置为-1,比如-1。这可能听起来微不足道,但想象一下某个对象的默认值(或任何值)需要很长时间来构建的情况......

我是否错过了一些明显的用例,我会设置我的默认值然后想让它被另一个我无法选择的覆盖?

4 个答案:

答案 0 :(得分:1)

out参数必须在声明它们的方法中明确设置。当然,它们通常设置为default(T)int为0。

答案 1 :(得分:1)

我最好的猜测是他们这样做是为了在调用方法之前不必初始化out参数。

他们本可以使用ref代替。但是,下面的代码将被破坏:

void Method() {
    int val;
    if(dict.TryGetValue("key", out val)) {
       Console.WriteLine(val);
    }
    return;
}

如果使用ref而不是out,则此代码会产生编译时错误,因为(per MSDNref个参数必须在传递之前初始化一种方法。

因为这是使用TryGetValue时非常常见的情况(即,你想尝试从集合中获取某些内容,如果它存在则执行某些操作,如果不存在则不执行任何操作),这是有道理的(至少对我来说)为什么他们使用out代替ref

答案 2 :(得分:1)

作为一般规则,您应该使用与您正在编写的方法兼容的最严格的方法签名。在outref的情况下,如果您不需要调用者为参数提供值,则如果使用out参数而不是使用ref参数,则该方法在概念上会更简单一个ref参数。在所有条件相同的情况下,输入越少,输出越少越好。

这种简单性在代码的可读性方面得到了回报,因为读者无需“回头”查看前一行代码的逻辑,以确定使用public Point3D Add(Point3D p1, Point3D p2) { return new Point3D(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z); } 传递的参数的传入值限定符。

另一个例子可能会使原则更清晰。通过引用传递参数比通过值传递参数更有效;对于较大的值类型,这可能是正确的。采取这种方法:

ref

public Point3D Add(ref Point3D p1, ref Point3D p2) { return new Point3D(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z); } 添加到每个参数中可能很诱人:

out

但是这会向消费者发送错误的消息,调用我们的方法,我们可能会修改方法体中的参数。我们还试图通过假设通过引用传递更有效来猜测优化器和JIT编译器。最好让代码清晰,让编译器完成它的工作。

有人可能会说,“在这种情况下是的,但那是不同的。”但原则是相同的:完全不需要限定符,并且在需要限定符时更喜欢refTryGetValue

对于if,如果调用包含在out中,使得当条件为false时不使用{{1}}参数,则该方法实际上可能会被内联并且完全省略了默认构造函数,这直接解决了我们所关心的效率。

与往常一样,规则有例外。如果您真的可以通过使用非严格必要的参数限定符来证明可衡量的性能增益,那么您可以记录实际行为,并接受由此导致的清晰度损失,那么它对于性能关键代码来说是可接受的权衡。

答案 3 :(得分:0)

查找失败时,您不会获得“未定义或异常行为”:out参数的值设置为default(TValue),方法返回false。您应该询问该返回值以确定查找是否成功。

不可否认,使用out参数会使TryGetValue感觉有些笨拙(例如,必须事先声明变量),但这是既定模式。我想你需要询问微软的BCL团队,如果你想知道为什么首先选择了这种模式。

无论如何,用一些你正在期待的辅助方法来“增强”TryGetValue是很容易的。 (如果您担心创建默认值的潜在成本,您可能会有一个接受工厂委托的重载,并且只在需要时才会创建值。)

例如:

// pass the default value directly
int i = foo.GetValueOrDefault("answer", 42);

// pass a delegate to create the default value on-demand
var v = bar.GetValueOrDefault("costly", x => SomeExpensiveOperation(x));

// ...

public static class DictionaryExtensions
{
    public static TValue GetValueOrDefault<TKey, TValue>(
        this IDictionary<TKey, TValue> source, TKey key, TValue defaultValue)
    {
        TValue value;
        return source.TryGetValue(key, out value) ? value : defaultValue;
    }

    public static TValue GetValueOrDefault<TKey, TValue>(
        this IDictionary<TKey, TValue> source,
        TKey key, Func<TKey, TValue> defaultFactory)
    {
        TValue value;
        return source.TryGetValue(key, out value) ? value : defaultFactory(key);
    }
}