重载函数以接受 IEnumerable、ICollection、IList等是否是常见(或鼓励)的做法?

发布于 2024-08-11 16:17:46 字数 1882 浏览 3 评论 0原文

编辑

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


假设我有一个函数的三个重载,一个采用 IEnumerable,一个采用 ICollection,一个采用 IList >,类似于以下内容:

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,这将使我的工作变得最简单;另一方面,我仍然可以使用 ICollection 甚至 IEnumerable 来完成这项工作;这些接口的实现效率不高。

我不确定这是否有效(运行时是否能够根据传递的参数选择重载),但我已经测试过它,似乎可以。

我的问题是:这个方法有没有我没有想到的问题?或者,这实际上是一个很好的方法,但是有更好的方法来实现它(也许首先尝试将 values 参数转换为 IList ,然后如果强制转换有效,则运行更有效的重载)?我只是有兴趣了解别人的想法。

EDIT:

From the answers given, it's been made rather clear to me how the design I'm asking about below should actually be implemented. With those suggestions in mind (and in response to a comment politely pointing out that my example code does not even compile), I've edited the following code to reflect what the general consensus seems to be. The question that remains may no longer make sense in light of the code, but I'm leaving it as it is for posterity.


Suppose I have three overloads of a function, one taking IEnumerable<T>, one taking ICollection<T>, and one taking IList<T>, something like the following:

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;
}

The idea here is that, if I've got an IList<T>, that makes my job easiest; on the other hand, I can still do the job with an ICollection<T> or even an IEnumerable<T>; the implementation for those interfaces just isn't as efficient.

I wasn't sure if this would even work (if the runtime would be able to choose an overload based on the parameter passed), but I've tested it and it seems to.

My question is: is there a problem with this approach that I haven't thought of? Alternately, is this in fact a good approach, but there's a better way of accomplishing it (maybe by attempting to cast the values argument up to an IList<T> first and running the more efficient overload if the cast works)? I'm just interested to know others' thoughts.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

黎夕旧梦 2024-08-18 16:17:46

如果您了解如何使用 Reflector 实现 LINQ 扩展方法,您会发现 IEnumerable上的一些扩展方法(例如 Count())尝试将序列转换为 ICollection类型。或 IList优化操作(例如,使用 ICollection.Count 属性,而不是迭代 IEnumerable并对元素进行计数)。因此,您最好的选择是最有可能接受 IEnumerable然后如果 ICollection则进行此类优化或IList可用。

If you have a look at how LINQ extension methods are implemented using Reflector, you can see that a few extension methods on IEnumerable<T>, such as Count(), attempt to cast the sequence to an ICollection<T> or an IList<T> to optimize the operation (for example, using the ICollection<T>.Count property instead of iterating through an IEnumerable<T> and counting the elements). So your best bet is most likely to accept an IEnumerable<T> and then do this kind of optimizations if ICollection<T> or IList<T> are available.

仙女 2024-08-18 16:17:46

我认为接受 IEnumerable的一个版本将是要走的路,并检查方法内部参数是否是更派生的集合类型之一。对于您建议的三个版本,如果有人向您传递(运行时)IList,您将失去效率优势。编译器静态地考虑 IEnumerable

        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

I think one version accepting IEnumerable<T> would be the way to go, and check inside the method if the parameter is one of the more derived collection types. With three versions as you propose, you lose the efficiency benefit if someone passes you a (runtime) IList<T> that the compiler statically considers an IEnumerable<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
坏尐絯℡ 2024-08-18 16:17:46

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

您可以接受 IEnumerable参数,并在内部检查它是否实际上是一个 ICollection;或IList,并相应地优化。

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

I'd say it's uncommon, and potentially confusing, so would be unlikely to be a good choice for a public API.

You could accept an IEnumerable<T> parameter, and internally check if it is in fact an ICollection<T> or IList<T>, and optimize accordingly.

This might be analagous to some of the optimizations in some of the IEnumerable<T> extension methods in the .NET 3.5 Framework.

我早已燃尽 2024-08-18 16:17:46

我真的很冷漠。如果我以你的方式看到它,我就不会想到任何事情。但乔的想法有其优点。它可能如下所示。

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)
{
}

I am really indifferent. If I saw it your way I would not think anything of it. But Joe's idea has merit. It might look like the following.

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)
{
}
还如梦归 2024-08-18 16:17:46

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

如果实现 ICollection 逻辑的最自然方式是为其编写一个特殊方法,那么拥有接受 ICollectionICollection< ;T>,并且如果给定一个实现 ICollection的对象,则让 IEnumerable 重载调用 ICollection。公开这样的超载不会增加太多价值,但也可能不会造成任何损害。另一方面,在对象同时实现 IEnumerableICollection,但不实现 ICollection 的情况下(例如, List 实现 IEnumerableICollection,但不实现 ICollection),一个可能想要使用这两个接口,但是如果没有在使用它们的方法中进行类型转换,或者向使用它们的方法传递一个 ICollection 引用和一个 IEnumerable接口,则无法完成此操作。参考。后者在公共方法中会非常丑陋,而前一种方法将失去重载的好处。

While it is legal to overload a method to accept either a base type or a derived type, with all other parameters being otherwise identical, it is only advantageous to do so if the compiler will often be able to identify the latter form as being a better match. Because it would be very common for objects which implement ICollection<T> to be passed around by code which only needs an IEnumerable<T>, it would be very common for implementations of ICollection<T> to be passed into the IEnumerable<T> overload. Consequently, the IEnumerable<T> overload should probably check whether a passed-in object implements ICollection<T> and handle then specially if so.

If the most natural way of implementing the logic for an ICollection<T> would be to write a special method for it, there would be nothing particularly wrong with having a public overload which accepts an ICollection<T>, and having the IEnumerable<T> overload call the ICollection<T> one if given an object that implements ICollection<T>. Having such an overload be public wouldn't add much value, but it likely wouldn't hurt anything either. On the other hand, in situations where an object implements both IEnumerable<T> and ICollection, but not ICollection<T> (for example, a List<Cat> implements IEnumerable<Animal> and ICollection, but not ICollection<Animal>), one might want to use both interfaces, but that could not be done without either typecasting in the method that uses them, or passing the method which uses them both an ICollection reference and an IEnumerable<T> reference. The latter would be very ugly in a public method, and the former approach would lose the benefits of overloading.

深巷少女 2024-08-18 16:17:46

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

如果您可以使用 IEnumerable来完成此操作,则使用它,如果不能,则使用所需的最少接口。

Usually when designing interfaces you want to accept a 'lowest common denominator' type for the arguments. For return types it is a matter of some debate. I generally think creating the above overloads is overkill. It's biggest problem is the introduction of unneeded code-paths that now must be tested. Better to have one method that performs the operation one way and works 100% of the time. With the given overloads above you might have an inconsistency in behavior and not even realize it, or worse yet you may accidentally introduce a change in one and not in the other copies.

If you can do it with IEnumerable<T> then use that, if not then use the least interface needed.

冷默言语 2024-08-18 16:17:46

不,这当然不常见。

反正。
由于 IList继承自 ICollection;和IEnumerable和ICollection继承自 IEnumerable,您唯一关心的是 IEnumerable中的性能类型。

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

当您重载函数时,只是提供一种传递不同类型参数的方法,如果该函数没有该签名,则无法将其传递给该函数。

除非确实必要,否则不要进行优化。
如果你想优化,就秘密进行。
您不会假装使用您的类的人知道该“优化”,以便决定使用哪个方法签名,对吧?

No. It's certainly uncommon.

Anyway.
Since IList<T> inherits from ICollection<T> and IEnumerable<T>, and ICollection<T> inherits from IEnumerable<T>, your only concern would be performance in IEnumerable<T> types.

I just see no reason to overload the function in that way, providing different signatures to achieve exactly the same result and accepting exactly the same types as parameter (no matter if you have an IEnumerable<T> or IList<T>, you would be able to pass it to any of the three overloads); that would just cause confusion.

When you overload a function, is just to provide a way to pass a different type of parameter that you cannot pass to the function if it would not have that signature.

Don't optimize unless it's really necessary.
If you want to optimize, do it undercover.
You won't pretend someone using your class to be aware of that "optimization" in order to decide which method signature to use, right?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文