2

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

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

这个函数的要点应该是它只需要在内部进行一次查找,而在容器外部编写的任何其他方法都必须进行两次查找,以便首先确定该值是否存在,然后再检索它(使用 try -抓住)。

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

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

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

4

4 回答 4

1

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

于 2013-04-02T02:49:05.297 回答
1

作为一般规则,您应该使用与您正在编写的方法兼容的最严格的方法签名。在outvs的情况下ref,如果您不需要调用者为参数提供值,则如果您使用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);
}

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

有人可能会说:“在那种情况下是的,但那是不同的。” 但原则是一样的:根本不喜欢限定词,而outref需要限定词时更喜欢。

在 的情况下TryGetValue,如果调用被包装在条件为假时不if使用out参数的情况下,则该方法实际上可能被内联并且默认构造函数完全被省略,这直接解决了我们无论如何关注的效率问题。

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

于 2013-04-02T05:58:45.317 回答
1

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

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

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

ref如果使用此代码代替 ,此代码将产生编译时错误out,因为(根据MSDNref参数必须在传递给方法之前进行初始化。

由于这是使用时非常常见的场景TryGetValue(即,您想尝试从集合中获取某些内容,如果存在则对其进行处理,如果不存在则不执行任何操作),因此(至少对我而言)为什么他们out代替ref. _

于 2013-04-02T02:53:36.823 回答
0

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

诚然,使用out参数让人TryGetValue感觉有点笨拙(例如,必须事先声明变量),但这是既定的模式。如果您想知道为什么首先选择该模式,我想您需要询问 Microsoft 的 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);
    }
}
于 2013-04-02T10:19:07.397 回答