具有相同名称和参数但具有不同结果和约束条件的多个通用方法

4
我目前正在重写自定义 RPC 机制的部分内容(无法用其他东西替换,所以请不要建议) 。调用的参数被收集在一个使用字典内部的自定义集合中。有一个方法 T Get<T>(string) 用于检索命名参数。对于可选参数,我想添加一个 TryGet<T>(string) 方法,如果参数不存在则返回参数或 null ,这样调用代码就可以使用空合并运算符提供默认值。当然,对于值类型,这种方法行不通,但我可以使用 T? 替代,这正是我想要的。
因此,我现在拥有的是:
public class Arguments
{
    // lots of other code here

    public T TryGet<T>(string argumentName) where T : class
    {
        // look up and return value or null if not found
    }

    public T? TryGet<T>(string argumentName) where T : struct
    {
        // look up and return value or null if not found
    }
}

因此,我希望能够实现以下操作:

return new SomeObject(
                      args.TryGet<string>("Name") ?? "NoName",
                      args.TryGet<int>("Index") ?? 1
                      );

由于这些限制是相互排斥的,编译器应该能够生成正确的代码(总是可以从调用站点给出的泛型类型推断出调用)。编译器会抱怨该类型已经定义了一个具有相同参数类型的成员“TryGet”。
有没有办法使这样的东西在不给两个方法不同的名称的情况下工作?
5个回答

7

编译器强制给出了答案,但我很高兴也找到了一个解释。谷歌把我带到了这里,但不是埃里克的博客。 - mdisibio

5
.NET Framework中的类处理这种情况的方式是使用带有out参数的TryGetValue方法。返回值表示获取是否成功,其中out参数包含所请求的值(成功时)或适当的默认值(失败时)。
该模式使得引用类型和值类型的实现非常简单。您只需要一个方法来处理两种情况。
有关此模式的示例,请参见Dictionary<TKey,TValue>.TryGetValue

好主意,但这意味着我无法在行内检索值,而我想要这样做(因为我想使用代码的示例)。在创建SomeObject之前,我需要声明变量并为每个值调用TryGetValue()。这使得代码比我喜欢的更大且不易读。我正在寻找更紧凑和优雅的解决方案。 - Pepor

3

这个方法无法正常工作的原因是,您不能有两个名称和参数类型相同的方法(方法重载时不考虑返回类型)。相反,您可以定义一个没有泛型约束的单个方法,该方法适用于值类型和引用类型:

public T TryGet<T>(string argumentName)
{
    if (!_internalDictionary.ContainsKey(argumentName))
    {
        return default(T);
    }
    return (T)_internalDictionary[argumentName];
}

1
这并不能为值类型提供所需的 Nullable <T> 行为。 - Jon Skeet
可以使用以下代码实现所需的行为:args.TryGet<int?>("Index") ?? 1 - Darin Dimitrov
你说得对,Jon,但是考虑到Darin的评论,至少这是我能接近我想象中的最接近的了。我想我和我的同事们也可以接受多一个字符的代码。 - Pepor

3
一种替代方案可能是这样的:
public class Arguments {
    public T Get<T>(string argumentName,T defaultValue) {
        // look up and return value or defaultValue if not found
    }
}

return new SomeObject(
    args.Get<string>("Name","NoName"),
    args.Get<int>("Index",1)
);

在这种情况下,您甚至不需要指定通用类型,因为它可以由默认参数推断出来。
return new SomeObject(
    args.Get("Name","NoName"),
    args.Get("Index",1)
);

这看起来不错。不幸的是,乍一看很难知道第二个参数的含义。当然,IntelliSense 提供了那个信息,但我希望我的代码可以容易理解,而不必依赖于文档。在我的代码库中有数千行的文档注释,我的同事从来没有花时间去阅读它们。;-) - Pepor
现在代码库已经迁移到 .Net Framework 4,我可以指定参数名称,像这样:args.Get("Name", defaultValue:"NoName")。这看起来很易读。 :-) - Pepor

1

虽然由于参数类型相同而无法直接工作,但您可以通过添加可选defaultValue参数来实现,该参数默认为null:

public class Arguments
{
    // lots of other code here

    public T? TryGet<T>(string argumentName, T? defaultValue = null) where T : class
    {
        // look up and return value or null if not found
    }

    public T? TryGet<T>(string argumentName, T? defaultValue = null) where T : struct
    {
        // look up and return value or null if not found
    }
}

这个方法有效的原因是两个约束的第二个参数类型不同(在具有class约束的方法中,它只是T,而在具有struct约束的方法中,它是Nullbale<T>)。
以下代码按照您的预期工作:
var args = new Arguments();

var stringValue = args.TryGet<string>("Name") ?? "NoName";

var intValue = args.TryGet<int>("Index") ?? 1;

太好了!即使我的问题已经12年了,这是我认为最好的答案。感谢您回复这么老的问题。 :-) - Pepor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接