What would be a practical advantage of using generics vs interfaces in this case:
void MyMethod(IFoo f) { } void MyMethod<T>(T f) : where T : IFoo { }
I.e. what can you do in MyMethod<T>
that you couldn't in the non-generic version? I'm looking for a practical example, I know what the theoretical differences are.
I know that in MyMethod<T>
, T will be the concrete type, but nonetheless I will only be able to use it as an IFoo within the body of the method. So what would be a real advantage?
Generics shift the burden of type safety from you to the compiler. There is no need to write code to test for the correct data type because it is enforced at compile time. The need for type casting and the possibility of run-time errors are reduced. Better performance.
Code that uses generics has many benefits over non-generic code: Stronger type checks at compile time. A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
Generic types perform better than normal system types because they reduce the need for boxing, unboxing, and type casting the variables or objects. Parameter types are specified in generic class creation.
IFoo
is a value type, the non-generic version will box the value of the parameter, and boxing can negatively affect performance (especially if you call this method very often)T
rather than a IFoo
, which is convenient if you need to call a method of T on the resultWell, one advantage as mentioned elsewhere, would be the ability to return a specific type of IFoo type if you return a value. But since your question is specifically about void MyMethod(IFoo f)
, I wanted to give a realistic example of at least one type of situation where using a generic method makes more sense (to me) than the interface. (Yes I spent a bit of time on this, but I wanted to try out some different ideas. :D)
There are two blocks of code, the first is just the generic method itself and some context, the second is the full code for the example, including lots of comments ranging from notes on possible differences between this and an equivalent non-generic implementation, as well as various things I tried while implementing that didn't work, and notes on various choices I made, etc. TL;DR and all that.
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 J.R.!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With