简体   繁体   中英

Generic method with type constraints or base class parameter

If I write a method accepting a parameter which derives from a BaseClass (or an interface), as far as I know there are two ways to achieve that:

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

and

void MyMethod(BaseClass obj) { ... }

What are the differences between the two methods?

In this example there isn't a big difference between the two, you can access the same members inside the method and you can call it with the same derived classes. There is a runtime difference as a generic method is compiled for each type it is invoked with.

Where generics come in useful would be if you would return a value depending on T

With generics you could do the following

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

Without this would be an error:

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

Note Although you mention constraining to a base class, it is worth mentioning that if you constrain not to a class, but to an interface, extra boxing will occur if the implementation is by a struct in the non generic version, this can have severe performance implications.

When T is constrained to a base class, there is not really much difference apart from what has already been stated.

When T is constrained to an interface, the difference can be huge:

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

struct Frob: IFrobbable { ... }

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

Definitely the example you quoted does not make much difference other than run time execution performance as mentioned in other answers.

Leaving aside generic collections benefits (performance improvement by avoiding boxing/unboxing for example) which we all aware of and we use frequently - Generics also works great from a consumer perspective. For example, the below code snippet is self explanatory to visualize API usage flexibility from a consumer perspective :

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 */
}

A couple of other use cases promoting flexibility while using generics along with constraints are :

Lets say I want to ensure parameter passed to method implements IAdd and IMultiply and I have class which implements both IAdd , IMulitply like :

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

If I need to go via non generic approach, I am forced to create redundant dummy interface like :

interface IDummy : IAdd, IMultiply

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

Isn't the former approach cleaner?

Also one more small thing just popped up while typing answer. In case you need to, how would you get new instance for parameter type inside method:

you cannot do

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

But with generic you could have; T temp = new T(); provided there is constraint of new()

Also what if you need a default value for parameter type?

you cannot do

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

But with generic you could have; var default = default(T)

As was said, it matters only once you get a return value. Consider these cases:

BaseClass MyMethod(BaseClass)

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

Compare it to this:

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

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

Strong Typification is one of the best friends you have in .NET. Embrace it. Never try to avoid it. The opposite would be cases like we have in PHP and JavaScript: http://www.sandraandwoo.com/2015/12/24/0747-melodys-guide-to-programming-languages/

In the examples included in your question, there isn't much difference between the generic and the non-generic version. But here are some other examples of method signatures that can't be expressed without generics:

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 { ... }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM