Используется для статических родовых классов?

212
10

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


например.


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

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


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


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


Резюме предоставленных ответов


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


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


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

спросил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
1
Решение
197

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


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

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

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
151

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


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


Например:


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 используют такие методы; но это также отлично подходит для (де) сериализаторов. Ограничение заключается в том, что вы получаете низкие накладные расходы, потому что вы привязываетесь к типу времени компиляции; если объект, который был передан, фактически является экземпляром подкласса, вы, вероятно, привязываетесь к неправильному типу - и добавление проверок, чтобы избежать такого рода подрывов в пользу низкого уровня служебных обязанностей.

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
129

Статические поля типичного типа относятся к фактическому типу 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();

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
112

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


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


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

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


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

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
91

В первом упоминании в блоге указано допустимое использование (как статический класс репозитория для реализации 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) { }
}

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
80

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



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


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


// 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> громоздкий и неуклюжий. Определение класса ванили немного больше, но клиентский код легче читать - благодаря типу вывода.


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


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


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

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
79

Я использую их, чтобы издеваться над 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);

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
66

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

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
46

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

ответил(а) 2021-01-19T23:05:49+03:00 9 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема