Самые важные вещи о дженериках C# ... усвоенный урок

Что самое важное вы знаете о дженериках: скрытые функции, типичные ошибки, лучшие и наиболее полезные практики, советы ...

Я начинаю реализовывать большую часть своей библиотеки / API, используя дженерики, и хотел бы собрать наиболее распространенные шаблоны, советы и т. Д., Которые можно найти на практике.

Позвольте мне формализовать вопрос: что самое важное вы узнали о дженериках?

Пожалуйста, приведите примеры - это будет легче для понимания, в отличие от запутанных и излишне сухих описаний.

Спасибо

Этот вопрос чем-то похож на вопрос Джона , но по другой теме.

Ответов (11)

Решение

Одна из самых важных вещей, которые я усвоил, - это то, что вы можете ограничивать параметры универсального типа . Это может быть очень мощным инструментом, позволяющим настраивать класс только для определенных типов объектов и позволяя использовать члены этого типа в вашем универсальном классе. Я понимаю, что это довольно фундаментально, но это одна из вещей, которые делают дженерики невероятно полезными.

Узнайте о возможностях и ограничениях вывода универсального типа в C#. Глубокое понимание того, что компилятор может, а что не может сделать на основе (например) типов параметров в вашем методе, может быть использовано для того, чтобы сделать общие варианты использования вашего API значительно более удобочитаемыми.

Прежде всего важно знать, как Generics работают в C#. Эта статья дает вам хороший обзор дженериков Андерса Хейлсберга (отца C#). Я не думаю, что использовать их как можно чаще - это так хорошо. Используйте дженерики, когда они действительно имеют смысл. Всегда помните KISS и YAGNI (Keep It Simple Stupid; вам это не понадобится) из экстремального программирования.

Я узнал, что дженерики - очень мощный инструмент, но неправильное их использование приводит к очень нечитаемому коду.

Два интересных урока. Первый; со списками; попробуйте мыслить категориями T ; для получения полной информации см. здесь , но вкратце вам нужно использовать:

public void Foo<T>(IList<T> data) where T : SomeBaseClassOrInterface {}

и не:

public void Foo(IList<SomeBaseClassOrInterface> data) {}

Во-вторых: следите за крайними случаями ;-p

Вы видите здесь ловушку?

static void Foo<T>() where T : new()
{
    T t = new T();
    Console.WriteLine(t.ToString()); // works fine
    Console.WriteLine(t.GetHashCode()); // works fine
    Console.WriteLine(t.Equals(t)); // works fine

    // so it looks like an object and smells like an object...

    // but this throws a NullReferenceException...
    Console.WriteLine(t.GetType()); // BOOM!!!
}

Универсальные типы делегатов всегда инвариантны по типу.

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

http://www.theserverside.net/blogs/thread.tss?thread_id=47323

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

class GenericType<T>
{
    public static int SomeValue;
}

Утверждение будет успешным, если мы сделаем это:

GenericType<int>.SomeValue = 3;
Debug.Assert(GenericType<double>.SomeValue == 0);

Это потому что:

typeof(GenericType<int>) != typeof(GenericType<double>)

Хотя

typeof(GenericType<int>.GetGenericTypeDefinition() == typeof(GenericType<double>).GetGenericTypeDefinition()

Не знаю, являются ли они самыми важными, но я узнал следующее:

Генерики можно будет создать через отражение только в том случае, если вы не знаете тип frikkin. В некоторых случаях вам могут потребоваться неуниверсальные интерфейсы для использования ваших универсальных классов в ситуациях, когда тип неизвестен.

Я чуть не разбил голову, пока не проглотил это

public class Foo<T> where T : Foo<T> {
  public T CloneMe() ...
}

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

public abstract class Machine<S,M> where S : State<S,M> where M : Machine<S,M>  {
    protected S state;
}

public abstract class State<S,M> where S : State<S,M> where M : Machine<S,M> {
    protected M machine;
}

Дженерики могут стать немного громоздкими. На днях у меня было такое:

List<Tuple<Expression<Func<DataTable,object>>,Expression<Func<DataTable,object>>>>

уф ...

MyGeneric<T> where T : IComparable

не делает

MyGeneric<IComparable> 

базовый класс этого.

Нет ковариации или контр-дисперсии (по крайней мере, в 3.5). Помните об этом при разработке иерархий классов, включающих параметры универсального типа.

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