首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >I防止使用泛型限制的特定类型。

I防止使用泛型限制的特定类型。
EN

Stack Overflow用户
提问于 2010-10-08 13:46:28
回答 4查看 408关注 0票数 6

我有一个重载方法-第一个实现总是返回单个对象,第二个实现总是返回一个枚举。

我想使方法泛化和重载,并限制编译器在泛型类型可枚举时试图绑定到非枚举方法.

代码语言:javascript
复制
class Cache
{
    T GetOrAdd<T> (string cachekey, Func<T> fnGetItem)
        where T : {is not IEnumerable}
    {
    }

    T[] GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem)
    {
    }
}

用在..。

代码语言:javascript
复制
{
    // The compile should choose the 1st overload
    var customer = Cache.GetOrAdd("FirstCustomer", () => context.Customers.First());

    // The compile should choose the 2nd overload
    var customers = Cache.GetOrAdd("AllCustomers", () => context.Customers.ToArray());
}

这只是我在这里侵犯的简单的坏代码味道,还是可以消除上述方法的歧义,这样编译器就可以始终正确地调用代码?

增加对除“重命名其中一种方法”以外的任何答案的人的选票。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-10-08 14:15:27

只使用一种方法,并让它动态地检测IEnumerable<T>情况,而不是通过泛型约束尝试不可能的情况。根据要存储/检索的对象是否是可枚举的对象,必须处理两个不同的缓存方法,这将是“代码嗅觉”。而且,仅仅因为它实现了IEnumerable<T>并不意味着它一定是一个集合。

票数 2
EN

Stack Overflow用户

发布于 2010-10-08 14:14:50

重命名其中一个方法。您会注意到,List<T>有一个Add和AddRange方法;遵循这个模式。对一项做某事和对一组项做某事在逻辑上是不同的任务,因此使方法具有不同的名称。

票数 8
EN

Stack Overflow用户

发布于 2010-10-08 14:31:28

--这是一个很难支持的用例,因为C#编译器如何执行重载解析,以及它如何决定要绑定到哪个方法。

第一个问题是方法的约束不是签名的一部分。,不会考虑过载解决。

您需要克服的第二个问题是编译器从可用的签名中选择最佳匹配--在处理泛型时,这通常意味着SomeMethod<T>(T)将被认为是比SomeMethod<T>( IEnumerable<T> )更好的匹配。特别是当您有像T[]List<T>这样的参数时。

但更根本的是,您必须考虑对单个值与值集合的操作是否真的是相同的operation.,如果它们在逻辑上是不同的,那么您可能只想使用不同的名称来澄清。也许有一些用例可以证明,单个对象和对象集合之间的语义差异是没有意义的.但在这种情况下,为什么要实现两种不同的方法呢?目前尚不清楚方法重载是否是表达差异的最佳方式。让我们来看看一个导致混乱的例子:

代码语言:javascript
复制
Cache.GetOrAdd("abc", () => context.Customers.Frobble() );

首先,请注意,在上面的示例中,我们选择忽略返回参数。其次,请注意,我们在Frobble()集合上调用了一些方法Customers。现在你能告诉我哪种超负荷的GetOrAdd()会被调用吗?显然,不知道Frobble()返回的类型是不可能的。就我个人而言,我认为,在可能的情况下,应该避免从语法中轻易推断出其语义的代码。如果我们选择更好的名字,这个问题就会得到缓解:

代码语言:javascript
复制
Cache.Add( "abc", () => context.Customers.Frobble() );
Cache.AddRange( "xyz", () => context.Customers.Frobble() );

最终只有三个选项可以消除示例中方法的歧义:

  1. 更改其中一个方法的名称。
  2. 在您调用第二次重载的任何地方,强制转换为IEnumerable<T>
  3. 以编译器可以区分的方式更改其中一个方法的签名。

选项1是不言自明的,所以我将不再谈论它。

选项2也很容易理解:

代码语言:javascript
复制
var customers = Cache.GetOrAdd("All", 
     () => (IEnumerable<Customer>)context.Customers.ToArray());

选项3更复杂。让我们来看看我们可以实现它的方法。

方法是通过更改Func<>委托的签名来实现,例如:

代码语言:javascript
复制
 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参数:

代码语言:javascript
复制
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);

但这真的更好吗?它阻止我们使用这些方法作为其他方法调用的参数。海事组织,它还使守则不那么明确,也更不容易理解。

最后一种选择是向方法中添加另一个泛型参数,该参数标识返回值的类型:

代码语言:javascript
复制
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());

因此可以使用提示来帮助编译器为我们选择重载.好的。但是,看看我们作为开发人员必须做的所有额外工作(更别提引入的丑陋和错误机会)了。真的值得吗?特别是当一种简单可靠的技术(将方法命名不同)已经存在来帮助我们的时候?

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3891157

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档