1

编辑

从给出的答案中,我已经很清楚我在下面询问的设计应该如何实际实施。考虑到这些建议(并回应礼貌地指出我的示例代码甚至无法编译的评论),我编辑了以下代码以反映普遍共识。鉴于代码,剩下的问题可能不再有意义,但我将其保留为后代。


假设我有一个函数的三个重载,一个 take IEnumerable<T>,一个takingICollection<T>和一个taking 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>,那会使我的工作变得最简单;另一方面,我仍然可以使用 anICollection<T>甚至 an来完成这项工作IEnumerable<T>;这些接口的实现效率不高。

我不确定这是否可行(如果运行时能够根据传递的参数选择重载),但我已经对其进行了测试,并且似乎可以。

我的问题是:这种方法有没有我没有想到的问题?或者,这实际上是一个好方法吗,但是有更好的方法来实现它(也许通过尝试将values参数转换为IList<T>第一个并在转换有效时运行更有效的重载)?我只是想知道别人的想法。

4

7 回答 7

5

如果您查看如何使用 Reflector 实现 LINQ 扩展方法,您会发现 IEnumerable<T> 上的一些扩展方法(例如 Count())尝试将序列强制转换为 ICollection<T> 或 IList< T> 来优化操作(例如,使用 ICollection<T>.Count 属性而不是遍历 IEnumerable<T> 并计算元素)。因此,您最好的选择是最有可能接受 IEnumerable<T>,然后在 ICollection<T> 或 IList<T> 可用的情况下进行此类优化。

于 2009-11-19T01:57:35.743 回答
2

我认为接受 IEnumerable<T> 的一个版本将是可行的方法,并在方法内部检查参数是否是更派生的集合类型之一。使用您建议的三个版本,如果有人向您传递编译器静态认为 IEnumerable<T> 的(运行时)IList<T>,您将失去效率优势:

        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
于 2009-11-19T02:08:59.307 回答
1

我会说它不常见,并且可能令人困惑,因此不太可能成为公共 API 的好选择。

您可以接受 IEnumerable<T> 参数,并在内部检查它是否实际上是 ICollection<T> 或 IList<T>,并进行相应优化。

这可能类似于 .NET 3.5 Framework 中某些 IEnumerable<T> 扩展方法中的某些优化。

于 2009-11-19T01:55:13.417 回答
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)
{
}
于 2009-11-19T02:13:33.213 回答
1

虽然重载方法以接受基类型或派生类型是合法的,但所有其他参数都相同,但只有在编译器通常能够识别出后一种形式更好时,这样做才有好处匹配。因为ICollection<T>通过只需要 的代码来传递实现的对象是很常见的IEnumerable<T>,所以将 的实现ICollection<T>传递给IEnumerable<T>重载是很常见的。因此,IEnumerable<T>重载可能应该检查传入的对象是否实现ICollection<T>并特别处理,如果是的话。

如果实现 an 逻辑的最自然方法是为其编写一个特殊方法,那么拥有一个接受 an 的公共重载并在给定一个实现的对象时让重载调用该重载ICollection<T>并没有什么特别错误的。公开这样的重载不会增加太多价值,但它也可能不会伤害任何东西。另一方面,在一个对象同时实现and而非实现的情况下(例如,a实现and但 not ),人们可能希望同时使用这两个接口,但如果没有在使用的方法中进行类型转换,则无法做到这一点它们,或传递同时使用它们的方法ICollection<T>IEnumerable<T>ICollection<T>ICollection<T>IEnumerable<T>ICollectionICollection<T>List<Cat>IEnumerable<Animal>ICollectionICollection<Animal>ICollection参考和IEnumerable<T>参考。后者在公共方法中会非常难看,而前一种方法会失去重载的好处。

于 2012-10-18T17:55:35.040 回答
0

通常在设计接口时,您希望接受参数的“最低公分母”类型。对于返回类型,这是一个有争议的问题。我通常认为创建上述重载是矫枉过正的。最大的问题是引入了现在必须测试的不需要的代码路径。最好有一种方法以一种方式执行操作并在 100% 的时间内工作。对于上面给定的重载,您可能会在行为上出现不一致甚至没有意识到,或者更糟糕的是,您可能会意外地在一个副本中引入更改,而不是在其他副本中引入。

如果你能做到,IEnumerable<T>那么就使用它,如果不能,那么使用所需的最少接口。

于 2009-11-19T01:54:46.927 回答
0

不,这当然不常见。

反正。由于 IList<T> 继承自 ICollection<T> 和 IEnumerable<T>,而 ICollection<T> 继承自 IEnumerable<T>,因此您唯一关心的是 IEnumerable<T> 类型的性能。

我只是认为没有理由以这种方式重载函数,提供不同的签名来实现完全相同的结果并接受完全相同的类型作为参数(无论你有一个 IEnumerable<T> 还是 IList<T>,你都会能够将其传递给三个重载中的任何一个);那只会引起混乱。

当你重载一个函数时,只是为了提供一种方法来传递不同类型的参数,如果它没有那个签名,你就不能传递给函数。

除非真的有必要,否则不要优化。如果你想优化,做卧底。你不会假装使用你的类的人知道那个“优化”来决定使用哪个方法签名,对吧?

于 2009-11-19T02:09:33.487 回答