generics与接口的实用优势

在这种情况下使用generics与接口有什么实际的优势:

void MyMethod(IFoo f) { } void MyMethod<T>(T f) : where T : IFoo { } 

也就是说,你可以在MyMethod<T>中做什么,你不能在非通用版本? 我正在寻找一个实际的例子,我知道理论上的差异是什么。

我知道在MyMethod<T> ,T将是具体types,但是我将只能在方法体内使用它作为IFoo。 那么这将是一个真正的优势?

那么其他地方提到的一个好处就是,如果你返回一个值,就可以返回一个特定types的IFootypes。 但是因为你的问题是关于void MyMethod(IFoo f)具体问题,所以我想给出一个现实的例子,至less有一种情况,使用generics方法比接口更有意义。 (是的,我花了一点时间,但是我想尝试一些不同的想法:D)

有两个代码块,第一个是通用方法本身和一些上下文,第二个是这个例子的完整代码,其中包括许多注释,这些注释和这个和一个等价的非generics实现之间的可能差异的注释,以及我在实施过程中尝试过的各种各样的事情,以及我做出的各种select的注释等等。TL,DR以及所有这些。

概念

  public class FooChains : Dictionary<IFoo, IEnumerable<IFoo>> { } // to manage our foos and their chains. very important foo chains. public class FooManager { private FooChains myChainList = new FooChains(); // void MyMethod<T>(T f) where T : IFoo void CopyAndChainFoo<TFoo>(TFoo fromFoo) where TFoo : IFoo { TFoo toFoo; try { // create a foo from the same type of foo toFoo = (TFoo)fromFoo.MakeTyped<TFoo>(EFooOpts.ForChain); } catch (Exception Ex) { // hey! that wasn't the same type of foo! throw new FooChainTypeMismatch(typeof(TFoo), fromFoo, Ex); } // a list of a specific type of foos chained to fromFoo List<TFoo> typedFoos; if (!myChainList.Keys.Contains(fromFoo)) { // no foos there! make a list and connect them to fromFoo typedChain = new List<TFoo>(); myChainList.Add(fromFoo, (IEnumerable<IFoo>)typedChain); } else // oh good, the chain exists, phew! typedChain = (List<TFoo>)myChainList[fromFoo]; // add the new foo to the connected chain of foos typedChain.Add(toFoo); // and we're done! } } 

血淋淋的细节

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IFooedYouOnce { // IFoo // // It's personality is so magnetic, it's erased hard drives. // It can debug other code... by actually debugging other code. // It can speak Haskell... in C. // // It *is* the most interesting interface in the world. public interface IFoo { // didn't end up using this but it's still there because some // of the supporting derived classes look silly without it. bool CanChain { get; } string FooIdentifier { get; } // would like to place constraints on this in derived methods // to ensure type safety, but had to use exceptions instead. // Liskov yada yada yada... IFoo MakeTyped<TFoo>(EFooOpts fooOpts); } // using IEnumerable<IFoo> here to take advantage of covariance; // we can have lists of derived foos and just cast back and // forth for adding or if we need to use the derived interfaces. // made it into a separate class because probably there will be // specific operations you can do on the chain collection as a // whole so this way there's a spot for it instead of, say, // implementing it all in the FooManager public class FooChains : Dictionary<IFoo, IEnumerable<IFoo>> { } // manages the foos. very highly important foos. public class FooManager { private FooChains myChainList = new FooChains(); // would perhaps add a new() constraint here to make the // creation a little easier; could drop the whole MakeTyped // method. but was trying to stick with the interface from // the question. void CopyAndChainFoo<TFoo>(TFoo fromFoo) where TFoo : IFoo // void MyMethod<T>(T f) where T : IFoo { TFoo toFoo; // without generics, I would probably create a factory // method on one of the base classes that could return // any type, and pass in a type. other ways are possible, // for instance, having a method which took two IFoos, // fromFoo and toFoo, and handling the Copy elsewhere. // could have bypassed this try/catch altogether because // MakeTyped functions throw if the types are not equal, // but wanted to make it explicit here. also, this gives // a more descriptive error which, in general, I prefer try { // MakeTyped<TFoo> was a solution to allowing each TFoo // to be in charge of creating its own objects toFoo = (TFoo)fromFoo.MakeTyped<TFoo>(EFooOpts.ForChain); } catch (Exception Ex) { // tried to eliminate the need for this try/catch, but // didn't manage. can't constrain the derived classes' // MakeTyped functions on their own types, and didn't // want to change the constraints to new() as mentioned throw new FooChainTypeMismatch(typeof(TFoo), fromFoo, Ex); } // a list of specific type foos to hold the chain List<TFoo> typedFoos; if (!myChainList.Keys.Contains(fromFoo)) { // we just create a new one and link it to the fromFoo // if none already exists typedFoos = new List<TFoo>(); myChainList.Add(fromFoo, (IEnumerable<IFoo>)typedFoos); } else // otherwise get the existing one; we are using the // IEnumerable to hold actual List<TFoos> so we can just // cast here. typedFoos = (List<TFoo>)myChainList[fromFoo]; // add it in! typedFoos.Add(toFoo); } } [Flags] public enum EFooOpts { ForChain = 0x01, FullDup = 0x02, RawCopy = 0x04, Specialize = 0x08 } // base class, originally so we could have the chainable/ // non chainable distinction but that turned out to be // fairly pointless since I didn't use it. so, just left // it like it was anyway so I didn't have to rework all // the classes again. public abstract class FooBase : IFoo { public string FooIdentifier { get; protected set; } public abstract bool CanChain { get; } public abstract IFoo MakeTyped<TFoo>(EFooOpts parOpts); } public abstract class NonChainableFoo : FooBase { public override bool CanChain { get { return false; } } } public abstract class ChainableFoo : FooBase { public override bool CanChain { get { return true; } } } // not much more interesting to see here; the MakeTyped would // have been nicer not to exist, but that would have required // a new() constraint on the chains function. // // or would have added "where TFoo : MarkIFoo" type constraint // on the derived classes' implementation of it, but that's not // allowed due to the fact that the constraints have to derive // from the base method, which had to exist on the abstract // classes to implement IFoo. public class MarkIFoo : NonChainableFoo { public MarkIFoo() { FooIdentifier = "MI_-" + Guid.NewGuid().ToString(); } public override IFoo MakeTyped<TFoo>(EFooOpts fooOpts) { if (typeof(TFoo) != typeof(MarkIFoo)) throw new FooCopyTypeMismatch(typeof(TFoo), this, null); return new MarkIFoo(this, fooOpts); } private MarkIFoo(MarkIFoo fromFoo, EFooOpts parOpts) : this() { /* copy MarkOne foo here */ } } public class MarkIIFoo : ChainableFoo { public MarkIIFoo() { FooIdentifier = "MII-" + Guid.NewGuid().ToString(); } public override IFoo MakeTyped<TFoo>(EFooOpts fooOpts) { if (typeof(TFoo) != typeof(MarkIIFoo)) throw new FooCopyTypeMismatch(typeof(TFoo), this, null); return new MarkIIFoo(this, fooOpts); } private MarkIIFoo(MarkIIFoo fromFoo, EFooOpts parOpts) : this() { /* copy MarkTwo foo here */ } } // yep, really, that's about all. public class FooException : Exception { public Tuple<string, object>[] itemDetail { get; private set; } public FooException( string message, Exception inner, params Tuple<string, object>[] parItemDetail ) : base(message, inner) { itemDetail = parItemDetail; } public FooException( string msg, object srcItem, object destType, Exception inner ) : this(msg, inner, Tuple.Create("src", srcItem), Tuple.Create("dtype", destType) ) { } } public class FooCopyTypeMismatch : FooException { public FooCopyTypeMismatch( Type reqDestType, IFoo reqFromFoo, Exception inner ) : base("copy type mismatch", reqFromFoo, reqDestType, inner) { } } public class FooChainTypeMismatch : FooException { public FooChainTypeMismatch( Type reqDestType, IFoo reqFromFoo, Exception inner ) : base("chain type mismatch", reqFromFoo, reqDestType, inner) { } } } // I(Foo) shot JR! 
  • 通过接口调用方法比直接调用具体types要慢
  • 如果实现IFoo的types是一个值types,那么非通用版本将会IFoo参数的值,而装箱会对性能产生负面影响(特别是如果您经常调用此方法)
  • 如果你的方法返回一个值,那么generics版本可以返回一个T而不是一个IFoo ,如果你需要调用结果T的方法

做这样的事情比较容易:

 void MyMethod<T>(T f) where T : IFoo, new() { var t1 = new T(); var t2 = default(T); // Etc... } 

此外,随着您引入更多的接口,generics可能对呼叫者更“温和”。 例如,你可以从2个接口inheritance一个类并直接传递它,像这样…

 interface IFoo { } interface IBar { } class FooBar : IFoo, IBar { } void MyMethod<T>(T f) where T : IFoo, IBar { } void Test() { FooBar fb = new FooBar(); MyMethod(fb); } 

而“仅接口”的方法将需要一个“中介”接口( IFooBar )…

 interface IFoo { } interface IBar { } interface IFooBar : IFoo, IBar { } class FooBar : IFooBar { } void MyMethod(IFooBar f) { } void Test() { FooBar fb = new FooBar(); MyMethod(fb); } 

2年后,我发现了一个非常简单而有用的案例。 考虑这个常见的模式:

 class MyClass : IDisposable { public void Dispose() { if (m_field1 != null) { m_field1.Dispose(); m_field1 = null; } if (m_field2 != null) { m_field2.Dispose(); m_field2 = null; } // etc } } 

我一直想写一个辅助方法来避免为每个字段写下所有的样板文件:

 class MyClass : IDisposable { static void IfNotNullDispose(ref IDisposable disposable) { if (disposable != null) { disposable.Dispose(); disposable = null; } } public void Dispose() { IfNotNullDispose(ref m_field1); IfNotNullDispose(ref m_field2); // etc } } 

不幸的是,这在C#中是非法的,因为你不能使用ref参数的接口,所以你必须使用你要传入的具体types,而不是别的。 所以你必须为每一种你想要处理的领域写一个不同的方法。 哦,等等,这就是generics为你做的:

 static void IfNotNullDispose<T>(ref T disposable) where T: class, IDisposable { if (disposable != null) { disposable.Dispose(); disposable = null; } } 

现在一切正常了!

在这个特殊情况下,没有任何好处。 一般来说,您不会在方法级别指定此值,而是在类级别指定。 例如,

 public interface IFoo { void DoSomethingImportant(); } public class MyContainer<T> where T : IFoo { public void Add(T something){ something.DoSomethingImportant(); AddThisThing(something); } public T Get() { T theThing = GetSomeKindOfThing(); return theThing; } } 

请注意,我们需要T实现IFoo,因为Add方法需要调用IFoo实现的DoSomethingImportantMethod。

但是请注意,在Get方法中,我们将返回由这个类的最终用户提供的T,而不是一个普通的旧IFoo,这减轻了开发者总是需要投入到他们实际的具体T.

例:

 public class Bar : IFoo{ //.... } MyContainer<Bar> m = new MyContainer<Bar>(); //stuff happens here Bar b = m.Get(); 

请注意,如果我只是返回一个IFoo,那么我将不得不在最后一行做到这一点:

 Bar b = (Bar) m.Get(); 

接口方法将为您提供一个IFootypes的f ,而通用版本将为您提供一个typesT ,约束T必须实现IFoo

第二种方法将允许你根据T某种查找,因为你有一个具体的types来处理。

指的是上面报告的基准

@Branko,通过一个接口调用一个方法实际上比>一个“正常的”虚拟方法调用要慢…这里有一个简单的基准:> pastebin.com/jx3W5zWb – Thomas Levesque

在Visual Studio 2015中运行代码的结果大致相当于Direct call和Through interface之间:

  • 直拨电话:90,51毫安; 112,49毫秒; 81,22毫秒
  • 通过接口:92,85毫升; 90,14毫秒; 88,56毫秒

用于基准testing的代码(来自http://pastebin.com/jx3W5zWb )是:

 using System; using System.Diagnostics; namespace test { class MainApp { static void Main() { Foo f = new Foo(); IFoo f2 = f; // JIT warm-up f.Bar(); f2.Bar(); int N = 10000000; Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < N; i++) { f2.Bar(); } sw.Stop(); Console.WriteLine("Through interface: {0:F2}", sw.Elapsed.TotalMilliseconds); sw.Reset(); sw.Start(); for (int i = 0; i < N; i++) { f.Bar(); } sw.Stop(); Console.WriteLine("Direct call: {0:F2}", sw.Elapsed.TotalMilliseconds); Console.Read(); } interface IFoo { void Bar(); } class Foo : IFoo { public virtual void Bar() { } } } } 

通用版本允许你使用任何types的T–你出于某种原因使用where子句来限制,而你的非通用版本支持实现IFoo的东西。

另一个(也许更好)的问题是 – 这两个选项是否相同