Общий метод с ограничениями типа или параметром базового класса

Если я напишу метод, принимающий параметр, который происходит из BaseClass (или интерфейса), насколько я знаю, есть два способа добиться этого:

void MyMethod<T>(T obj) where T : BaseClass { ... }

а также

void MyMethod(BaseClass obj) { ... }

Какие преимущества/недостатки используют один над другим?

Ответ 1

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

Где генерические приходы полезны, было бы, если бы вы вернули значение в зависимости от T

С помощью дженериков вы можете сделать следующее

T MyMethod<T>(T obj) where T : BaseClass { ... }
MyMethod(derivedInstance).derivedProperty

Без этого была бы ошибка:

BaseClass MyMethod(BaseClass obj) { ... }
MyMethod(derivedInstance).derivedProperty // error

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

Ответ 2

Когда T ограничено базовым классом, разница, отличная от того, что уже было указано, не имеет большого значения.

Когда T ограничено интерфейсом, разница может быть огромной:

int FrobNonGeneric(IFrobbable frob) { //... }
int Frob<T>(T frob) where T: IFrobbable { //... }

struct Frob: IFrobbable { ... }

FrobNonGeneric(new Frob()); //boxing!
Frob(new Frob()); //no boxing

Ответ 3

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

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

interface IEntity
{
   int Id {get;set;}
}

class Student : IEntity
{
   int Id {get;set;}
   string SubjectOpted {get;set;}
}

class Employee : IEntity
{
   int Id {get;set;}
   string DepartmentName{get;set;}
}

interface INonGenericRepository
{
   IEntity Get(int id)
}

interface IGenericRepository<T> where T:Entity
{
   T Get(int id)
}

class NonGenericRepository : IRepository
{
   public IEntity Get(int id) {/*implementation goes here */
}

class GenericRepository<T> : IRepository<T>
{
   public T Get(int id) {/*implementation goes here */
}

Class NonGenericStudentConsumer
{
   IEntity student = new NonGenericFRepository().Get(5);
   var Id = student.Id
   var subject = student.SubjectOpted /*does not work, you need to cast */
}

Class GenericStudentConsumer
{
   var student = new GenericFRepository<Student>().Get(5);
   var Id = student.Id
   var subject = student.SubjectOpted /*works perfect and clean */
}

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

Допустим, я хочу, чтобы параметр, переданный методу реализует IAdd и IMultiply и у меня есть класс, который реализует как IAdd, так и IMulitply:

    public class BusinessOpeartion<T> where T : IAdd, IMultiply{
    void SomeBusinessOpeartion(T obj) { /*implementation */}
}

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

interface IDummy : IAdd, IMultiply

 public class BusinessOpeartion{
        void SomeBusinessOpeartion(IDummy obj) { /*implementation */}
    }

Разве бывший подход не подходит?

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

вы не можете сделать

IDummy dummy = new IDummy(); /*illegal*/

Но с общим вы могли бы иметь; T temp = new T(); при условии, что существует ограничение new()

Также, если вам нужно значение по умолчанию для типа параметра?

вы не можете сделать

var default = default(IDummy); /*illegal*/

Но с общим вы могли бы иметь; var default = default(T)

Ответ 4

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

BaseClass MyMethod(BaseClass)

DervivedClass temp = new DervivedClass();
//Error. My Method always returns a BaseClass. No implicit casting available
temp = MyMethod(temp);

Сравните это с этим:

T MyMethod<T>(T) where T: BaseClass

DervivedClass temp = new DerivedClass();
temp = MyMethod<DerivedClass>(temp);

Сильная типизация - один из лучших друзей в.NET. Прими это. Никогда не пытайтесь избежать этого. Противоположными были бы случаи, подобные нам в PHP и JavaScript: http://www.sandraandwoo.com/2015/12/24/0747-melodys-guide-to-programming-languages/

Ответ 5

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

T MyMethod<T>(T obj) where T : BaseClass { ... } 
void MyMethod<T>(T obj1, T obj2) where T : BaseClass { ... } 
void MyMethod<T>(T obj, List<T> list) where T : BaseClass { ... }