这是一个难以支持的使用案例,因为C#编译器如何执行重载决策和绑定方法。
第一个问题是
约束不是方法签名的一部分,不会被考虑在重载决策中。
第二个问题是编译器从可用签名中选择最佳匹配,通常意味着在处理泛型时,
SomeMethod<T>(T)
将被认为比
SomeMethod<T>( IEnumerable<T> )
更匹配...特别是当你有像
T[]
或
List<T>
这样的参数时。
但更基本的是,你必须考虑操作单个值与操作一组值是否真的是相同的操作。如果它们在逻辑上不同,那么为了清晰起见,您可能需要使用不同的名称。也许有一些用例可以认为单个对象和对象集合之间的语义差异并不重要...但在这种情况下,为什么要实现两种不同的方法呢?不清楚方法重载是否是表达差异的最佳方式。让我们看一个容易混淆的例子:
Cache.GetOrAdd("abc", () => context.Customers.Frobble() );
首先,注意上面的示例中我们选择忽略返回参数。其次,请注意我们在Customers
集合上调用了一些方法Frobble()
。现在你能告诉我哪个重载的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[])
Customer customer;
Cache.GetOrAdd("First", _ => context.Customers.First(), out customer);
Customer[] customers;
var customers = Cache.GetOrAdd("All",
() => context.Customers.ToArray(), out customers);
但这真的更好吗?它阻止我们将这些方法作为其他方法调用的参数使用。在我看来,它也使代码变得不太清晰和不易理解。
我将提出最后一个替代方案,即向方法添加另一个通用参数,用于标识返回值的类型:
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());
所以我们可以使用提示来帮助编译器为我们选择重载...当然。但是看看我们作为开发人员需要做的所有额外工作(更不用说引入的丑陋和错误机会)。这真的值得吗?特别是当已经存在一种简单可靠的技术(命名方法不同)来帮助我们时?
string
类型的项目,因为string
实现了IEnumerable
和IEnumerable<char>
。 - herzmeistervoid
和返回类型。如果没有void
,你将试图对返回类型进行重载,这是不允许的,甚至不考虑一般性问题。 - AakashM