这是一个难以支持的用例,因为 C# 编译器如何执行重载解析以及它如何决定绑定到哪个方法。
第一个问题是约束不是方法签名的一部分,并且不会被考虑用于重载解决方案。
您必须克服的第二个问题是编译器从可用签名中选择最佳匹配 - 在处理泛型时,这通常意味着SomeMethod<T>(T)
将被认为比...更好的匹配SomeMethod<T>( IEnumerable<T> )
...特别是当您有像这样的参数时T[]
或List<T>
。
但更根本的是,您必须考虑对单个值和一组值进行操作是否真的是相同的操作。如果它们在逻辑上不同,那么您可能希望使用不同的名称只是为了清楚起见。也许在某些用例中,您可能会认为单个对象和对象集合之间的语义差异没有意义……但在这种情况下,为什么要实现两种不同的方法呢?尚不清楚方法重载是表达差异的最佳方式。让我们看一个导致混淆的例子:
Cache.GetOrAdd("abc", () => context.Customers.Frobble() );
首先,请注意,在上面的示例中,我们选择忽略返回参数。其次,注意我们Frobble()
在Customers
集合上调用了一些方法。现在你能告诉我GetOrAdd()
将调用哪个重载吗?显然不知道Frobble()
返回它的类型是不可能的。就我个人而言,我认为应该尽可能避免使用无法从语法中轻易推断出语义的代码。如果我们选择更好的名称,这个问题就会得到缓解:
Cache.Add( "abc", () => context.Customers.Frobble() );
Cache.AddRange( "xyz", () => context.Customers.Frobble() );
最终,只有三个选项可以消除示例中的方法的歧义:
- 更改其中一种方法的名称。
- 投射到
IEnumerable<T>
你称之为第二个重载的任何地方。
- 以编译器可以区分的方式更改其中一种方法的签名。
选项1是不言而喻的,所以我不再多说。
选项2也很容易理解:
var customers = Cache.GetOrAdd("All",
() => (IEnumerable<Customer>)context.Customers.ToArray());
选项 3 更复杂。让我们看看我们可以实现它的方法。
方法是通过更改Func<>
委托的签名,例如:
T GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem)
T[] GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem)
// now we can do:
var customer = Cache.GetOrAdd("First", _ => context.Customers.First());
var customers = Cache.GetOrAdd("All", () => context.Customers.ToArray());
就个人而言,我发现这个选项非常丑陋、不直观且令人困惑。引入一个未使用的参数是可怕的......但是,遗憾的是它会起作用。
更改签名的另一种方法(不那么糟糕)是将返回值作为out
参数:
void GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem, out T);
void GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem, out T[])
// now we can write:
Customer customer;
Cache.GetOrAdd("First", _ => context.Customers.First(), out customer);
Customer[] customers;
var customers = Cache.GetOrAdd("All",
() => context.Customers.ToArray(), out customers);
但这真的更好吗?它阻止我们将这些方法用作其他方法调用的参数。IMO,它还使代码变得不那么清晰和难以理解。
我将提出的最后一个替代方法是向方法中添加另一个通用参数,以标识返回值的类型:
T GetOrAdd<T> (string cachekey, Func<T> fnGetItem);
R[] GetOrAdd<T,R> (string cachekey, Func<IEnumerable<T>> fnGetItem);
// now we can do:
var customer = Cache.GetOrAdd("First", _ => context.Customers.First());
var customers = Cache.GetOrAdd<Customer,Customer>("All", () => context.Customers.ToArray());
所以可以使用提示来帮助编译器为我们选择重载……当然。但是看看我们作为开发人员必须做的所有额外工作才能到达那里(更不用说引入的丑陋和犯错的机会)。真的值得努力吗?特别是当已经存在一种简单可靠的技术(以不同的方式命名方法)来帮助我们时?