Использует для статических универсальных классов? - PullRequest
24 голосов
/ 21 апреля 2010

Каковы основные применения Статического Универсального класса в C #? Когда их следует использовать? Какие примеры лучше всего иллюстрируют их использование?

например.

public static class Example<T>
{
   public static ...
}

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

http://ayende.com/Blog/archive/2005/10/05/StaticGenericClass.aspx

Статический родовой класс как словарь


Краткое изложение ответов

Основные проблемы заключаются в следующем: «В чем разница между статическим универсальным классом со статическими методами и неуниверсальным статическим классом со статическими универсальными элементами ?"

Решение о том, какое использование использовать, похоже, вращается вокруг "Нужно ли классу хранить внутреннее состояние определенного типа?"

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

Ответы [ 9 ]

24 голосов
/ 21 апреля 2010

Я использую статические универсальные классы для кэширования кода с интенсивным отражением.

Допустим, мне нужно построить дерево выражений, которое создает экземпляры объектов.Я строю его один раз в статическом конструкторе класса, компилирую в лямбда-выражение, затем кеширую в член статического класса.Я часто не делаю эти классы общедоступными для оценки - они обычно являются помощниками для других классов.Кэшируя мои выражения таким образом, я избегаю необходимости кэшировать мои выражения в каком-то виде Dictionary<Type, delegate>.

. В этом примере есть BCL .(DataRow) методы расширения Field<T>() и SetField<T>() используют (приватный) статический универсальный класс System.Data.DataRowExtensions+UnboxT<T>.Проверьте это с помощью отражателя.

13 голосов
/ 21 апреля 2010

Создание класса static не добавляет никакой функциональности - это просто удобная проверка, если вы намереваетесь использовать класс без его создания. И для этого есть несколько применений ...

Вы можете использовать статические универсальные классы, чтобы обойти ограничение: C # не допускает частичной специализации. Это означает, что вы должны либо указать все параметры типа, либо ни одного. Однако это может быть излишне многословным.

Например:

static class Converter {
    public TOut Convert<TIn, TOut>(TIn x) {...}
}

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

static class ConvertTo<TOut> {
    public TOut Convert<TIn>(TIn x) {...}
}

Таким образом, вы можете позволить выводу типа работать с типом параметра и указывать только возвращаемый тип.

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


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

Полустатическая таблица поиска по типам звучит немного загадочно, но на самом деле это очень и очень полезная структура, поскольку она позволяет хранить дорогостоящие результаты отражения и генерации кода там, где их почти можно искать (дешевле) чем словарь, потому что он получает JIT-ed, и вы избегаете вызова .GetType()). Если вы занимаетесь метапрограммированием, это здорово!

Например, я использую это в ValueUtils для хранения сгенерированных хеш-функций:

//Hash any object:
FieldwiseHasher.Hash(myCustomStructOrClass);

//implementation:
public static class FieldwiseHasher {
    public static int Hash<T>(T val) { return FieldwiseHasher<T>.Instance(val); }
}

public static class FieldwiseHasher<T> {
    public static readonly Func<T, int> Instance = CreateLambda().Compile();
    //...
}

Статические универсальные методы позволяют вывод типа , чтобы сделать использование действительно простым; статические поля в универсальных классах позволяют практически беспрепятственно хранить (мета) данные . Меня совсем не удивило бы, если бы ORM, например, Dapper и PetaPoco использовали такие методы; но это также отлично подходит для (де) сериализаторов. Ограничение состоит в том, что вы получаете низкие накладные расходы, потому что привязываетесь к типу время компиляции ; если переданный объект на самом деле является экземпляром подкласса, вы, вероятно, привязываетесь к неправильному типу - и добавление проверок, чтобы избежать такого рода, подрывает преимущество низких издержек.

10 голосов
/ 21 апреля 2010

Статические поля универсального типа специфичны для фактического типа T. Это означает, что вы можете хранить кеш конкретного типа внутри. Это может быть причиной для создания статического универсального типа. Вот (довольно бесполезный, но информативный) пример:

public static TypeFactory<T> where T : new()
{
    // There is one slot per T.
    private static readonly object instance = new T();

    public static object GetInstance() {
        return instance;
    }
}

string a = (string)TypeFactory<string>.GetInstance();
int b = (int)TypeFactory<int>.GetInstance();
5 голосов
/ 21 апреля 2010

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

Например, если вы собираетесь создать статический универсальный класс и знаете, что все универсальные типы должны быть IComparable (просто пример), тогда вы можете сделать следующее.

static class MyClass<T> where T : IComparable
{
  public static void DoSomething(T a, T b)
  {
  }

  public static void DoSomethingElse(T a, T b)
  {
  }
}

Обратите внимание, что мне не нужно было применять ограничение ко всем членам, но только на уровне класса.

3 голосов
/ 21 апреля 2010

Первое упомянутое вами сообщение в блоге показывает правильное использование (в качестве статического класса репозитория для реализации ActiveRecord):

public static class Repository<T>
{
    public static T[] FindAll { }

    public static T GetById(int id){ }

    public static void Save(T item) { }
}

Или, если вы хотите реализовать различные методы сортировки для универсальных массивов:

public static class ArraySort<T>
{
    public static T[] BubbleSort(T[] source) { }

    public static T[] QuickSort(T[] source) { }
}
2 голосов
/ 23 ноября 2016

Я использую их для проверки DbSet при тестировании классов, использующих методы EntityFramework Async.

public static class DatabaseMockSetProvider<TEntity> where TEntity: class
{
    public static DbSet<TEntity> CreateMockedDbSet(IQueryable<TEntity> mockData)
    {
        var mockSet = Mock.Create<DbSet<TEntity>>();
        Mock.Arrange(() => ((IDbAsyncEnumerable<TEntity>)mockSet).GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<TEntity>(mockData.GetEnumerator()));
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Provider)
            .Returns(new TestDbAsyncQueryProvider<TEntity>(mockData.Provider));
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Expression).Returns(mockData.Expression);
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).ElementType).Returns(mockData.ElementType);
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).GetEnumerator()).Returns(mockData.GetEnumerator());

        return mockSet;
    }
}

И использовать их так же, как в моих модульных тестах - экономит много времени и может использовать их для любых типов сущностей:

var mockSet = DatabaseMockSetProvider<RecipeEntity>.CreateMockedDbSet(recipes);
        Mock.Arrange(() => context.RecipeEntities).ReturnsCollection(mockSet);
2 голосов
/ 21 апреля 2010

Каковы основные виды использования статического универсального класса в C #?Когда их следует использовать?Какие примеры лучше всего иллюстрируют их использование?

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

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

// static class with generics
public static class ListOps<T>
{
    public static List<T> Filter(List<T> list, Func<T, bool> predicate) { ... }
    public static List<U> Map<U>(List<T> list, Func<T, U> convertor) { ... }
    public static U Fold<U>(List<T> list, U seed, Func<T, U> aggregator) { ... }
}

// vanilla static class
public static class ListOps
{
    public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate) { ... }
    public static List<U> Map<T, U>(List<T> list, Func<T, U> convertor) { ... }
    public static U Fold<T, U>(List<T> list, U seed, Func<T, U> aggregator) { ... }
}

Но классы эквивалентны, но какой из них легче использовать?Сравните:

// generic static class
List<int> numbers = Enumerable.Range(0, 100).ToList();
List<int> evens = ListOps<int>.Filter(numbers, x => x % 2 = 0);
List<string> numberString = ListOps<int>.Map(numbers, x => x.ToString());
int sumOfSquares = ListOps<int>.Fold(numbers, 0, (acc, x) => acc + x*x);

// vanilla static class
List<int> numbers = Enumerable.Range(0, 100).ToList();
List<int> evens = ListOps.Filter(numbers, x => x % 2 = 0);
List<string> numberString = ListOps.Map(numbers, x => x.ToString());
int sumOfSquares = ListOps.Fold(numbers, 0, (acc, x) => acc + b * b);

На мой взгляд, ListOps<someType> громоздкий и неуклюжий.Определение класса vanilla немного больше, но клиентский код легче читать - благодаря выводу типа.

В худшем случае C # не может определить типы, и вы должны указать их вручную.Вы бы предпочли написать ListOps<A>.Map<B>(...) или ListOps.Map<A, B>(...)?Я предпочитаю последнее.


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

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

1 голос
/ 21 апреля 2010

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

0 голосов
/ 21 апреля 2010

Статический обобщенный класс точно так же полезен, как и любой данный статический класс. Разница в том, что вам не нужно использовать копирование и вставку для создания версии статического класса для каждого типа, с которым вы хотите, чтобы он работал. Вы делаете класс общим и можете «генерировать» одну версию для каждого набора параметров типа.

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