Универсальные методы в C #, не вызывающие наиболее строгой перегрузки - PullRequest
1 голос
/ 05 июня 2019

У меня перегружены универсальные методы, которые выглядят так:

public static T DoSomething<T>(T val) 
{
    return val;
}

public static IEnumerable<T> DoSomething<T>(IEnumerable<T> vals) 
{
    return vals.Select(x => DoSomething(x));
}

Проблема, с которой я сталкиваюсь, заключается в том, что следующий код не будет компилироваться:

var myList = new List<SomeObject>();

// This will not compile
PropertyOfTypeIEnumerable_SomeObject = MyStaticClass.DoSomething(myList);

Компилятор жалуется, что не может конвертировать SomeObject в IEnumerable<SomeObject>. Это указывает на то, что компилятор выбирает первую версию DoSomething, которая принимает универсальный тип T, в отличие от второй версии, которая принимает более ограничительный универсальный тип IEnumerable.

Я могу подтвердить это, переименовав второй перегруженный метод в DoSomethingList и явно вызвав это имя:

public static T DoSomething<T>(T val) 
{
    return val;
}

public static IEnumerable<T> DoSomethingList<T>(IEnumerable<T> vals) 
{
    return vals.Select(x => DoSomething(x));
}

// ...

var myList = new List<SomeObject>();

// This compiles ok
PropertyOfTypeIEnumerable_SomeObject = MyStaticClass.DoSomethingList(myList);

Мой вопрос заключается в том, почему компилятор не выбирает наиболее ограничивающую подходящую универсальную реализацию для вызова, и есть ли способ, которым я могу получить такое поведение, не называя его однозначно и не вызывая его явно? Если бы в примере вызывались перегрузки для разных объектов, которые унаследованы друг от друга, тогда он выбрал бы наиболее ограничительную перегрузку на основе объявленного типа переменной. Почему он не делает это и для дженериков?

Другим вариантом было бы не перегружать методы, а проверять внутри метода DoSomething, чтобы увидеть, можно ли присвоить T из IEnumerable <>, но тогда я не знаю, как на самом деле привести его к чему-то, что я мог бы вызвать Включен метод Linq, и я не знаю, как заставить компилятор быть в порядке с возвратом результата этого метода Linq, поскольку I будет знать, что возвращаемый результат должен быть IEnumerable, но компилятор не будет.

1 Ответ

1 голос
/ 05 июня 2019

Обобщения в C # во многом отличаются от шаблонов C ++ (1) .Вы были бы правы, если бы мы работали в метапрограммировании на C ++, это вызвало бы более ограничительную функцию IEnumerable<T>.Однако в C # функция <T> лучше соответствует List<T>, потому что она более точна.

Я протестировал ваш код выше, и он прекрасно компилируется для меня в .NET v4.0.30319, но возвращает *Тип 1008 * вместо ожидаемого уменьшенного IEnumerable<T> типа, который возвращает вызов Select.Указывая на то, что была вызвана функция <T>.

Если вы хотите выполнить DoSomething для всех объектов в IEnumerable расширенных классах, то есть способ сделать это:

public static T DoSomething<T>(T val)
{
    switch (val)
    {
        case IEnumerable vals:
            foreach (object x in vals)
                DoSomething(x);
            break;
    }

    return val;
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...