谁能解释一下.NET';TryGetValue是多少?

谁能解释一下.NET';TryGetValue是多少?,.net,collections,.net,Collections,有人能告诉我为什么.NET泛型集合上的TryGetValue函数即使在查找失败时也会设置out值吗 在我看来,我们使用TryGetValue的全部原因是,我可以设置一个初始默认值/失败值,并且只有在查找实际成功时,才使用TryGetValue更改该值。这是函数的Try部分。如果我希望在查找失败时出现未定义或异常的行为,我就不会使用*Try*GetValue 此函数的要点应该是,它只需要在内部进行一次查找,而在容器外部编写的任何其他方法都必须进行两次查找,以便首先确定值是否存在,然后检索它(使用t

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

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

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

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

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


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

out
参数必须在声明它们的方法中显式设置。当然,它们通常被设置为
default(T)
,这对于
int
来说是0。

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

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

void Method() {
    int val;
    if(dict.TryGetValue("key", out val)) {
       Console.WriteLine(val);
    }
    return;
}
如果此代码使用
ref
而不是
out
,则会产生编译时错误,因为(per)
ref
参数在传递给方法之前必须初始化


由于在使用
TryGetValue
时,这是一种非常常见的情况(即,您想尝试从集合中获取一些东西,如果它存在,则对其进行处理,如果不存在,则不进行任何处理),因此(至少对我而言)他们使用
out
而不是
ref
的原因是有道理的

一般来说,您应该使用与您正在编写的方法兼容的最严格的方法签名。在
out
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编译器。最好让代码清晰,让编译器完成它的工作

有人可能会说,“在这种情况下是的,但那是不同的。”但原则是一样的:当需要限定符时,根本不喜欢使用限定符,而更喜欢使用
out
to
ref

TryGetValue
的情况下,如果调用被包装在
if
中,使得当条件为false时不使用
out
参数,那么该方法实际上可能是内联的,并且默认构造函数完全被省略,这直接解决了我们所关心的效率问题

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

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

诚然,使用
out
参数会让
TryGetValue
感觉有点笨拙(例如,必须事先声明变量),但这是既定的模式。我想你应该问问微软的BCL团队,你是否想知道为什么会选择这种模式

无论如何,用一些辅助方法“增强”
TryGetValue
是很容易的,这些方法可以实现您的期望。(如果您担心创建默认值的潜在成本,您可能会有一个重载,它接受factory委托,并且仅在需要时创建该值。)

例如:

// 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);
    }
}
//直接传递默认值
int i=foo.GetValueOrDefault(“答案”,42);
//传递委托以按需创建默认值
var v=bar.GetValueOrDefault(“昂贵”,x=>SomeExpensiveOperation(x));
// ...
公共静态类字典扩展
{
公共静态TValue GetValuerDefault(
此IDictionary源、TKey键、TValue默认值)
{
t价值;
返回源.TryGetValue(键,输出值)?值:defaultVal