Разрешение перегрузки C# с IList и IReadOnlyList

у меня есть метод, который я хотел бы взять все объекты, подобные списку, в моем решении. До .NET 4.5 это было просто:

public static T Method<T>(IList<T> list)
{
    // elided
}

однако .NET 4.5 представил IReadOnlyList<T>, к которому этот метод также должен применяться.

Я не могу просто изменить подпись, чтобы взять IReadOnlyList<T>, поскольку есть места, где я применяю метод к чему-то конкретно набранному как IList<T>.

алгоритм не может работать на IEnumerable<T>, и он используется слишком часто (и слишком большие объекты) взять IEnumerable<T> и создать новый List<T> при каждом вызове.

Я попытался добавить перегрузку:

public static T Method<T>(IReadOnlyList<T> list)
{
    // elided
}

... но это не будет компилироваться ни для чего, что реализует оба интерфейса (T[], List<T> и многие другие типы), поскольку компилятор не может определить, какой метод использовать (особенно раздражает, поскольку они имеют одно и то же тело, поэтому это не имеет значения).

Я не хочу добавлять перегрузки Method где взять T[], и List<T>, и каждый другой тип, который реализует оба интерфейса.

как я должен это сделать?

4 ответов


Это может быть один из тех случаев, когда на самом деле проверка типа среды выполнения полезна:

public static T Method<T>(IEnumerable<T> source)
{
    if (source is IList<T> list)
        return Method(list);

    if (source is IReadOnlyList<T> readOnly)
        return Method(readOnly);

    return Method(source.ToList() as IList<T>);
}

private static T Method<T>(IReadOnlyList<T> list) { ... }
private static T Method<T>(IList<T> list) { ... }

вам все равно придется дублировать код в том смысле, что вам нужны отдельные реализации для IList и IReadOnlyList потому что нет общего интерфейса, который вы можете использовать, но вы, по крайней мере, избегаете неоднозначной проблемы вызова.


ваш вероятный лучший выбор - сделать глобальный поиск и заменить IList до IReadOnlyList. Если нет ошибок компиляции, то вы должны быть в порядке.

вы должны получать только ошибки компилятора, если вы используете IList.Add - что в любом случае безрассудно, так как массивы не поддерживают Add.


можете ли вы изменить код вызова метода? Что делать, если вы создадите такой метод:

    public static T1 Method<T1, T2>(T2 list) where T2 : IList<T1>, IReadOnlyList<T1>
    {
        return default(T1);
    }

в этом случае вызовы выглядят следующим образом:

List<string> listA = new List<String>();
ReadOnlyCollection<string> listB = listA.AsReadOnly();

string outVar1 = Method<string, List<string>>(listA);
string outVar2 = Method<string, ReadOnlyCollection<string>>(listB);

другой способ создать два метода расширения для IList и IReadOnlyList таким образом:

    public static T Test<T>(this IList<T> source)
    {
        return default(T);
    }

    public static T Test<T>(this IReadOnlyList<T> source)
    {
        return default(T);
    }

и называйте их так:

    string outVar1 = (listA as IReadOnlyList<string>).Test();
    string outVar2 = (listB as IList<string>).Test();

возможно, ваше лучшее решение-посмотреть, почему ваш алгоритм не может работать на IEnumerable и изменить это. Вы используете IList<T> или IReadOnlyList<T> - конкретные члены, которые можно заменить членами, доступными в IEnumerable<T>? Например:

// instead of
int c = list.Count;

// use
int c = list.Count();

EDIT: игнорируйте глупости ниже. Я оставляю это, чтобы комментарии продолжали иметь смысл.

вы не должны реализовать как IList<T> и IReadOnlyList<T> в любом классе. Единственные дополнительные члены в IList спецификация предназначена для записи в список. Вы не должны делать это, если ваш список только для чтения. Я думаю, вам нужно изменить любые классы, которые реализуют оба, чтобы при их использовании можно было выбрать правильный метод.