修改:
从给出的答案中,我已经明确地知道我在下面提出的设计应该如何实施。考虑到这些建议(并且回应一条评论,礼貌地指出我的示例代码甚至没有编译),我编辑了以下代码以反映普遍的共识似乎是什么。根据代码,剩下的问题可能已经不再有意义了,但是我将其留给后人。
假设我有一个函数的三个重载,一个执行IEnumerable<T>
,一个执行ICollection<T>
,一个执行IList<T>
,如下所示:
public static T GetMiddle<T>(IEnumerable<T> values) {
IList<T> list = values as IList<T>;
if (list != null) return GetMiddle(list);
int count = GetCount<T>(values);
T middle = default(T);
int index = 0;
foreach (T value in values) {
if (index++ >= count / 2) {
middle = value;
break;
}
}
return middle;
}
private static T GetMiddle<T>(IList<T> values) {
int middleIndex = values.Count / 2;
return values[middleIndex];
}
private static int GetCount<T>(IEnumerable<T> values) {
// if values is actually an ICollection<T> (e.g., List<T>),
// we can get the count quite cheaply
ICollection<T> genericCollection = values as ICollection<T>;
if (genericCollection != null) return genericCollection.Count;
// same for ICollection (e.g., Queue<T>, Stack<T>)
ICollection collection = values as ICollection;
if (collection != null) return collection.Count;
// otherwise, we've got to count values ourselves
int count = 0;
foreach (T value in values) count++;
return count;
}
这里的想法是,如果我有一个IList<T>
,这使我的工作变得最容易;另一方面,我仍然可以使用ICollection<T>
甚至是IEnumerable<T>
来完成工作;这些接口的实现效率不高。
我不确定这是否会起作用(如果运行时能够根据传递的参数选择重载),但我已经测试了它,似乎。
我的问题是:我没有想到这种方法有问题吗?或者,这实际上是一种很好的方法,但是有一种更好的方法可以实现它(可能是首先尝试将values
参数转换为IList<T>
并运行更有效的重载)?我只是想知道别人的想法。
答案 0 :(得分:5)
如果您看一下如何使用Reflector实现LINQ扩展方法,您可以看到IEnumerable&lt; T&gt;上的一些扩展方法,例如Count(),尝试将序列转换为ICollection&lt; T&gt;。或者IList&lt; T&gt;优化操作(例如,使用ICollection&lt; T&gt; .Count属性而不是迭代IEnumerable&lt; T&gt;并计算元素)。所以你最好的选择是最有可能接受IEnumerable&lt; T&gt;然后,如果ICollection&lt; T&gt;,则进行这种优化。或IList&lt; T&gt;可用。
答案 1 :(得分:2)
我认为一个版本接受IEnumerable&lt; T&gt;将是要走的路,并检查方法内部是否参数是更多派生的集合类型之一。如果有人通过你的(运行时)IList&lt; T&gt;,你提出三个版本就会失去效率优势。编译器静态地考虑IEnumerable&lt; T&gt;:
IList<string> stringList = new List<string> { "A", "B", "C" };
IEnumerable<string> seq = stringList;
Extensions.GetMiddle(stringList); // calls IList version
Extensions.GetMiddle(seq); // calls IEnumerable version
答案 2 :(得分:1)
我认为这种情况并不常见,而且可能会让人感到困惑,因此不太可能成为公共API的理想选择。
您可以接受IEnumerable&lt; T&gt;参数,并在内部检查它是否实际上是ICollection&lt; T&gt;或IList&lt; T&gt;,并相应地进行优化。
这可能与某些IEnumerable&lt; T&gt;中的某些优化有关。 .NET 3.5 Framework中的扩展方法。
答案 3 :(得分:1)
我真的无动于衷。如果我按照你的方式看到它,我就不会想到它。但乔的想法很有道理。它可能如下所示。
public static T GetMiddle<T>(IEnumerable<T> values)
{
if (values is IList<T>) return GetMiddle((IList<T>)values);
if (values is ICollection<T>) return GetMiddle((ICollection<T>)values);
// Use the default implementation here.
}
private static T GetMiddle<T>(ICollection<T> values)
{
}
private static T GetMiddle<T>(IList<T> values)
{
}
答案 4 :(得分:1)
虽然重载方法以接受基类型或派生类型是合法的,但所有其他参数在其他方面都相同,但只有在编译器通常能够将后一种形式识别为是一个更好的匹配。因为实现ICollection<T>
的对象通过仅需要IEnumerable<T>
的代码传递是很常见的,所以将ICollection<T>
的实现传递给{是非常常见的{1}}超载。因此,IEnumerable<T>
重载应该检查传入的对象是否实现了IEnumerable<T>
,然后特别处理。
如果为ICollection<T>
实现逻辑的最自然的方法是为它编写一个特殊的方法,那么使用接受ICollection<T>
的公共重载并没有什么特别的错误,并且如果给定一个实现ICollection<T>
的对象,则IEnumerable<T>
重载会调用ICollection<T>
。公开这样的过载不会增加太多价值,但它也不会伤害任何东西。另一方面,在对象实现ICollection<T>
和IEnumerable<T>
但不实现ICollection
的情况下(例如,ICollection<T>
实现List<Cat>
和{{ 1}},但不是IEnumerable<Animal>
),人们可能想要使用这两个接口,但如果没有在使用它们的方法中进行类型转换,或者传递使用它们的方法{{1} }引用和ICollection
引用。后者在公共方法中会非常难看,前一种方法会失去重载的好处。
答案 5 :(得分:0)
通常在设计接口时,您希望接受参数的“最小公分母”类型。对于返回类型,这是一个有争议的问题。我一般认为创建上述重载是过度的。最大的问题是引入了不必要的代码路径,现在必须对其进行测试。最好有一种方法以单向方式执行操作,并且100%的时间都可以工作。对于上面给定的重载,您可能会出现行为不一致甚至没有意识到,或者更糟糕的是,您可能会意外地在一个副本中引入更改而不是在其他副本中引入更改。
如果你可以使用IEnumerable<T>
,那么使用它,如果没有,那么使用所需的最少接口。
答案 6 :(得分:0)
没有。这当然不常见。
反正。 由于IList&lt; T&gt;继承自ICollection&lt; T&gt;和IEnumerable&lt; T&gt;,和ICollection&lt; T&gt;继承自IEnumerable&lt; T&gt;,您唯一关心的是IEnumerable&lt; T&gt;中的性能。类型。
我认为没有理由以这种方式重载函数,提供不同的签名来实现完全相同的结果并接受与参数完全相同的类型(无论你是否有IEnumerable&lt; T&gt;或IList&lt; T&gt;,你可以将它传递给三个重载中的任何一个);这只会引起混淆。
当你重载一个函数时,只是提供一种方法来传递一个不同类型的参数,如果它没有那个签名就不能传递给它。
除非真的有必要,否则不要进行优化。 如果你想进行优化,请进行秘密处理。 您不会假装有人使用您的班级来了解“优化”以决定使用哪种方法签名,对吧?