【问题标题】:Constraint vs Parameter - way to force collection as parameter约束与参数 - 强制收集作为参数的方法
【发布时间】:2014-11-17 15:57:33
【问题描述】:

我想知道这两种方法之间是否有任何区别。第二个看起来更自然,但这不应该是使用它的唯一原因。也许有一些性能问题或一些与其中任何一个相关的恶魔 mambojambo?

void FirstMethod<T>(T a) where T : IEnumerable<Animal>
{
    ...
}

void SecondMethod<T>(IEnumerable<T> a) where T : Animal
{
    ...
}

【问题讨论】:

  • 这将取决于你对参数做了什么。两者并不相同,但差异可能与特定实现密切相关,也可能不相关。两者在功能上有所不同,因此性能不是主要问题,而是它们实际会做的事情。

标签: c# generics collections


【解决方案1】:

不同的是,你可以很容易地将IEnumerable&lt;Dog&gt;传递给第二种方法, 但是当你将它传递给第一个方法时,它只会被隐式转换为IEnumerable&lt;Animal&gt;

看看fiddle

已编辑感谢@Servy 发表评论。

【讨论】:

  • 您也可以将IEnumerable&lt;Dog&gt; 传递给第一个,它只会被隐式转换为IEnumerable&lt;Animal&gt;
  • IEnumerable&lt;T&gt; 被声明为out(协变)。可能需要IEnumerable&lt;Dog&gt;
  • 定义为IEnumerable&lt;out T&gt;,表示它是协变的。您可以将IEnumerable&lt;Dog&gt; 转换为IEnumerable&lt;Animal&gt;
  • 你说他们接受不同的类型。从签名中可以看出这一点。这仍然没有说明任何有意义的差异。
  • 那么,由于集合的接口是变体的,它们之间有什么真正的区别吗?
【解决方案2】:

我在 LinqPad 中尝试过这个示例:

void Main()
{   
    var cats = new [] { new Cat() };
    FirstMethod(cats);
    SecondMethod(cats);
}
interface Animal
{
}
class Cat : Animal
{
}
void FirstMethod<T>(T a) where T : IEnumerable<Animal>
{
    var b = a.FirstOrDefault();
}
void SecondMethod<T>(IEnumerable<T> a) where T : Animal
{
    var b = a.FirstOrDefault();
}

通过查看生成的 IL 代码,这 2 个方法调用之间没有区别,它们都接受 cats 作为参数。

编辑:差异出现在方法中,如下所示。 (感谢@servy 的评论)

IL_001D:  ldarg.0     
IL_001E:  ldloc.0     // cats
IL_001F:  call        UserQuery.FirstMethod
IL_0024:  nop         
IL_0025:  ldarg.0     
IL_0026:  ldloc.0     // cats
IL_0027:  call        UserQuery.SecondMethod

FirstMethod:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  box         02 00 00 1B 
IL_0007:  call        System.Linq.Enumerable.FirstOrDefault
IL_000C:  stloc.0     // b
IL_000D:  ret         

SecondMethod:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  call        05 00 00 2B 
IL_0007:  stloc.0     // b
IL_0008:  ret         

在此 IL 代码上使用反编译器可显示装箱发生的位置以确保类型安全。

private void Main()
{
    Cat[] a = new Cat[1]
    {
        new Cat()
    };
    this.FirstMethod<Cat[]>(a);
    this.SecondMethod<Cat>((IEnumerable<Cat>) a);
}

private void FirstMethod<T>(T a) where T : IEnumerable<Animal>
{
    Enumerable.FirstOrDefault<Animal>((IEnumerable<Animal>) a);
}

private void SecondMethod<T>(IEnumerable<T> a) where T : Animal
{
    Enumerable.FirstOrDefault<T>(a);
}

【讨论】:

  • 都编译。它们不相同(除了这两种方法什么都不做)。
  • @Servy 我不确定你为什么说它们不相同。我添加了 IL 代码来说明。这两种方法在调用和执行方面都是相同的(a 是 IEnumerable
  • 他们接受不同的类型,有不同的行为,可以用他们接受的参数做不同的事情,等等。他们都编译并不意味着他们是相同的。它们在您的示例中相同的唯一原因是它们都没有任何实现。如果他们有实现,他们就会有各种不同的方式。
  • 事实上,参数a在这两种情况下都结束了IEnumerable&lt;Animal&gt;的实现。我同意这些方法中的每一种都可以做不同的事情,因为它们具有不同的签名,但是没有理由根据性能或其他恶魔般的原因选择一种而不是另一种,因为它们最终会产生相同的 IL 代码。
  • 不,参数最终不会属于同一类型。在您自己的代码中,第二种方法接受IEnumerable&lt;Cat&gt; 而不是IEnumerable&lt;Animal&gt;。如果您将cats 键入为Animal[],那么第一种方法实际上就是该类型,而第二种方法将是IEnumerable&lt;Animal&gt;。这些差异与什么都不做的方法无关。它们可能与另一个实现密切相关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多