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

27
如果我编写一个接受从BaseClass(或接口)派生的参数的方法,据我所知有两种实现方法:
void MyMethod<T>(T obj) where T : BaseClass { ... }

并且

void MyMethod(BaseClass obj) { ... }

两种方法有什么不同之处?

3
我不知道为什么这个被投票认为"过于宽泛",但我会补充一些背景信息:在阅读了Anders Hejlsberg有关C#中泛型的采访之后,我想知道使用这两种提议的方法会有什么差异(例如在代码生成方面)。 - Thomas Flinkow
3
有一些东西被认为是离题的,其中“哪个更好”的问题就是其中之一。这与“哪个更好”的问题很接近,只不过实际上存在句法差异,人们可能一开始会简单地忽略它。或者他们只是觉得这个问题太“泛泛”了吧? - Christopher
6个回答

22
在这个例子中,两者之间没有太大的区别,您可以在方法内部访问相同的成员,并且可以使用相同的派生类调用它。运行时会有一个差异,因为泛型方法是针对每种类型进行编译的。
泛型非常有用的地方在于,如果您要根据 T 返回一个值。
使用泛型,您可以执行以下操作:
T MyMethod<T>(T obj) where T : BaseClass { ... }
MyMethod(derivedInstance).derivedProperty

没有这个会出现错误:

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

注意 尽管你提到了约束到基类,但值得一提的是,如果你将约束不是到一个类而是到一个接口,如果实现是在非泛型版本中由结构体完成,则会产生额外的装箱操作,这可能会对性能产生严重影响。


11

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

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 接口,而我有一个类实现了这两个接口,如下所示:

    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/


1
我现在使用适当的方法签名使代码更清晰。 - Christopher

2
在您提供的示例中,泛型版本和非泛型版本之间没有太大的区别。但是以下是一些其他方法签名的示例,如果没有泛型,将无法表达:
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 { ... }

0
另一件你可以用泛型做而可空参数无法做的事情是获取参数的类型:typeof(T)总是成功的,而obj.GetType()如果objnull则会抛出异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接