Использование аргумента generic type вместо аргумента типа System.Type. Это запах?

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

Например, рассмотрим следующий метод:

public string GetTypeName(System.Type type) { return type.FullName; }

этот метод часто сопровождается общей версией:

public string GetTypeName<T>() { return GetTypeName(typeof(T)); }

Вопросы это плохая практика или хорошая практика?
Является ли это синтаксическим сахаром или там больше?

Я вижу это как неправильное использование языковой функции для сокращения вызова метода, который принимает аргумент типа System.Type

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

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

  • Если добавлен аргумент типа non System.Type - метод, возможно, потребуется переписать (если порядок аргументов семантически значим) для не общей версии (в противном случае некоторые аргументы будут аргументами общего типа, а некоторые будут регулярными аргументами).
  • для этого требуются два метода (общий и не общий для случаев, когда тип неизвестен во время компиляции). Следовательно, добавляет модульные тесты, которые в основном бессмысленны.

С другой стороны, это обычная практика (и большинство всегда правы, верно?), но, что более важно, ReSharper предпочитает эту подпись, когда я делаю рефакторинг Extract Method на коде, который требует одного аргумента типа System.Type, известного при компиляции (и я научился брать их рекомендации, хотя не по вере, но серьезно).

Ответ 1

Думаю, вам нужно рассмотреть документацию. Насколько очевидно, что делают методы? Если у вас есть два метода (один с Type и один с аргументом типа), пользователи должны смотреть на оба и выбирать. Люди, которые не смотрят на ваш код, могут не понимать, что второй просто вызывает первый.

Если это определенно имеет смысл использовать оба, это когда аргумент типа фактически используется, когда он может быть, и есть какой-то откат для версии Type. Например:

object GetThingOfType(Type type) { ... }

T GetThingOfType<T>() { return (T)GetThingOfType(typeof(T)); }

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

var t = typeof(string);
var name = GetTypeName(t);
var assemblyName = t.Assembly.FullName;

Даже если я знаю, что тип string, я не должен писать GetTypeName<string> здесь, потому что я буду повторять сам. Предоставляя мне вариант, который я чаще всего предпочитаю не выбирать, вы добавляете немного ненужной сложности.

Более неясным моментом является поддержка XML-документа в среде IDE. Вы документируете аргумент типа следующим образом:

<typeparam name="T">important information</typeparam>

Затем, если вы наберете GetTypeName< в С#, Visual Studio покажет "T: важная информация". Но по какой-то причине, когда вы вводите GetTypeName(Of в Visual Basic, он не будет (начиная с 2012 года).

Ответ 2

Вы правы: рассмотрите семантику метода. Работает ли он на экземплярах типа или на самом типе?

Если он работает с экземплярами, то это должен быть общий метод. Если он принадлежит типу, сделайте его аргументом типа Type.

Итак, в вашем примере я бы сказал

public string GetTypeName(System.Type type) { return type.FullName; }

В то время как

public static int Count<TSource>(this IEnumerable<TSource> source)

Работает на экземпляре source типа IEnumerable<TSource>.

В общем, я видел, как дженерики злоупотребляют более чем хорошо используемыми. Любая реализация общего метода, которая делает типof (T) или еще хуже, использует любое отражение, на мой взгляд, не является общей и является злоупотреблением. В конце концов, generic означает, что он работает одинаково независимо от аргумента типа, не так ли?

Итак, в общем, я согласен с тобой - пахнет.

Ответ 3

Я не использую шаблон string GetName<T>() { return typeof(T).Name; }, поскольку это неправильное использование (неправильное использование, вероятно, сильное, но я не могу придумать правильное слово) шаблона проектирования, который является причиной генериков, а именно: параметры типового типа существуют для компилятора и JITter (см. ответ на этот вопрос), чтобы они могли создавать хранилище типов, параметры, стековые переменные и т.д. и т.д.

Использование его как удобного метода для передачи аргумента типа во время выполнения мне пахнет. Бывают случаи, когда typeof(T) необходимо, но я обнаружил, что они редки и обычно необходимы только при выполнении сложных вещей с помощью дженериков, в отличие от простых типов безопасности. Если я это увижу, я обязательно остановлюсь и спрошу себя, почему он там.

Ответ 4

Методы, которые относятся к типам, обычно делают именно это: Работа с типами.

IMO, Class.Method<SomeType>(); намного лучше, чем Class.Method(typeof(SomeType));

Но это вопрос мнения, я думаю.

Рассмотрим LINQ .OfType<T>(), например:

personlist.OfType<Employee>().Where(x => x.EmployeeStatus == "Active");

против

personlist.OfType(typeof(Employee)).Where(x => ((Employee)x).EmployeeStatus == "Active");

который вы бы предпочли?

Ответ 5

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

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

Конечно, это просто личное мнение.

Ответ 6

Общий метод имеет преимущество перед методом с параметром типа Type, поскольку он может ссылаться на другие общие вещи, используя предоставленный тип. Это особенно полезно в следующем сценарии:

public string GetTypeName<T>()
{ 
    return Cache<T>.TypeName;
}

private static class Cache<T>
{
    public static readonly TypeName = GetTypeName(typeof(T));
}

Этот кеш прост, нет необходимости возиться со словарями, и он автоматически потокобезопасен.

Говоря это, если реализация не использует эту возможность, то разница между ними просто косметическая.