вторник, 15 января 2019 г.

Максимально ограничивайте область Generic-а и не используйте в перегрузках методов

Вчера исправлял баг, проявил он себя весьма интересно. Но продемонстрировать его можно на достаточно простом примере:

static void Main(string[] args)
{
    Print(new[] { 1, 2, 3 });
    Console.ReadKey();
}

static void Print<T>(T t)
{
    Console.WriteLine(t);
}

static void Print<T>(IEnumerable<T> t)
{
    foreach (var item in t)
    {
        Console.WriteLine(item);
    }
}
При запуске вместо ожидаемых 1, 2, 3 мы увидим:
Т.к. мы не задали ограничений на T, то и IEnumerable тоже приводится к T объявленному в первом методе. Накладывание ограничений на T позволило бы узнать о этой проблеме на этапе компиляции. Изменим код:
class Demo
{
    public int X { get; set; }
}

static void Main(string[] args)
{
    Print(new[] { new Demo { X = 1 }, new Demo { X = 2 }, new Demo { X = 3 } });
    Console.ReadKey();
}

static void Print<T>(T t) where T : Demo
{
    Console.WriteLine(t);
}

static void Print<T>(IEnumerable<T> t) where T : Demo
{
    foreach (var item in t)
    {
        Console.WriteLine(item);
    }

}
В данном примере я явно указал к чему должен приводиться T (к Demo или его потомкам). И вот вместо ошибки времени выполнения, мы получаем ошибку компиляции:
Error CS0311 The type 'ConsoleApp1.Program.Demo[]' cannot be used as type parameter 'T' in the generic type or method 'Program.Print(T)'. There is no implicit reference conversion from 'ConsoleApp1.Program.Demo[]' to 'ConsoleApp1.Program.Demo'.

Как написано в заголовке, надо быть аккуратнее с Generic, максимально накладывать на них ограничения и не использовать в перегруженных методах, особенно в таком виде. А то вместо строгой типизации получим не строгую.

5 комментариев:

  1. А смысл от параметра в методе, не использующем этот параметр? Если уберем его, полагаю, все прекрасно скомпилируется и будет работать как ожидается.

    ОтветитьУдалить
    Ответы
    1. В смысле не использующемся? t маленькое используется для вывода значений в консоль. То что особенности T большого не используются, ну так это демо пример, важно проблему показать. А так можно внутри написать не просто вывод, а для последнего фрагмента кода, например так: Console.WriteLine(item.X);

      Удалить
    2. думаю вопрос бы к тому, что в типе аргумента метода Print IEnumerable использует не Generic тип, по этому во 2 методе T не имеет смысла.

      Удалить
    3. Блин, блогер проглатил скобки, сейчас попробую поправить.

      Удалить