具有类型约束或基类参数的通用方法

时间:2018-02-12 14:21:23

标签: c# generics inheritance

如果我编写一个方法来接受一个派生自BaseClass(或接口)的参数,据我所知有两种方法可以实现:

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

void MyMethod(BaseClass obj) { ... }

使用一个优于另一个的优点/缺点是什么?

5 个答案:

答案 0 :(得分:17)

在这个示例中,两者之间没有很大的区别,您可以访问方法内的相同成员,并且可以使用相同的派生类调用它。由于为其调用的每种类型编译了泛型方法,因此存在运行时差异。

如果您根据T

返回值,那么泛型会变得有用

使用泛型,您可以执行以下操作

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

如果没有这将是一个错误:

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

注意虽然您提到了对基类的约束,但值得一提的是,如果不限制类,而是限制接口,如果实现是通过结构实现的,则会发生额外的限制非通用版本,这可能会产生严重的性能影响。

答案 1 :(得分:8)

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

答案 2 :(得分:5)

除了其他答案中提到的运行时执行性能之外,您所引用的示例绝对没有太大区别。

不考虑通用集合的好处(例如通过避免装箱/拆箱来提高性能)我们都知道并经常使用 - 从消费者的角度来看,泛型也很有用。例如,以下代码片段可以自我解释,从消费者的角度可视化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 */
}

在使用泛型和约束时提高灵活性的其他一些用例包括:

假设我想确保传递给方法实现IAddIMultiply的参数,并且我有一个实现IAddIMulitply的类,如:

    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)

答案 3 :(得分:3)

正如所说,只有获得回报价值才有意义。考虑以下情况:

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);

Strong Typification是您在.NET中最好的朋友之一。接受它。永远不要试图避免它。相反的情况就像我们在PHP和JavaScript中的情况:http://www.sandraandwoo.com/2015/12/24/0747-melodys-guide-to-programming-languages/

答案 4 :(得分:1)

在您的问题中包含的示例中,通用版和非通用版之间没有太大区别。但是这里有一些方法签名的例子,它们不能用泛型表达:

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