Practical Advantage of Generics VS Interfaces

Practical advantage of generics vs interfaces

Well, 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.

Concept

    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!
}
}

Gory Details

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.!

Generics vs. Interfaces

I wouldn't say anything was generics vs interfaces, each have their different needs and uses. Using generic parameters in the way mentioned in the original post serves multiple purposes. It allows developers to define the base classes that make up the field object types of the class. Using this, the developer can accept class objects as parameters which they can use reflection on to create the actual objects and set the fields, or just accept the entire object to set in the first place. The problem which relates to needing class objects rather than doing new T() or such is known as type erasure.

Another benefit of using generics is that you don't need to typecast all the time when using fields or methods from a superclass -- you personally know their types, but Java doesn't have that type information stored anywhere. An additional benefit is that all your getter / setter methods can use the generic parameters and expose a more sensible front to other objects which rely on the fact that you set up specialised fields in the aforementioned object.

The problem with using interfaces to do the same thing that generics does is that you need additional methods to access the specialised types and cast them, before returning them (or check incoming types and then set fields to the objects). It's making the design more complex, and doesn't really help with decoupling at all.

As I mentioned in a comment, any subclasses will set those type parameters and expose nothing to the user. So you can have something like class MegaSignupWizard extends SignupWizard<MegaSignupSection, MegaSignupObject, MegaSignupListener, MegaSignupView> and everything remains perfectly valid with MegaSignupWizard having access to specialised methods in the classes without any need to cast. Now that's cool :)

What is cool about generics, why use them?

  • Allows you to write code/use library methods which are type-safe, i.e. a List<string> is guaranteed to be a list of strings.
  • As a result of generics being used the compiler can perform compile-time checks on code for type safety, i.e. are you trying to put an int into that list of strings? Using an ArrayList would cause that to be a less transparent runtime error.
  • Faster than using objects as it either avoids boxing/unboxing (where .net has to convert value types to reference types or vice-versa) or casting from objects to the required reference type.
  • Allows you to write code which is applicable to many types with the same underlying behaviour, i.e. a Dictionary<string, int> uses the same underlying code as a Dictionary<DateTime, double>; using generics, the framework team only had to write one piece of code to achieve both results with the aforementioned advantages too.

What is the difference between a parameter and T generic one with constraint? C#

What is the point difference here and what are the advantages of using generic one with constraint over a simple one?

With generics, multiple constraints can be specified:

private void Send<T>(T packet) where T : IPacket, IFoo {
}
...

private void Send<T>(T packet) where T : IPacket, new() {
var t1 = new T();
var t2 = default(T);
}

There is also a small performance win when you use generics because direct calls tend to be faster than those made via interfaces.

Are there drawbacks to the generic-implementing-non-generic-interface pattern?

In retrospect, I've learned that the correct term for what I want is return type covariance, which is unfortunately not supported in C#, because the language design team does not consider the benefits of implementing the feature outweigh the cost, even though it preserves type safety. (A proposal has been drafted and completed, but it seems to be abandoned).

With return type covariance, the example code could be written as:

class Foo { }

class Bar
{
public virtual Foo Foo { get; }
}

class Bar<TFoo> : Bar where TFoo : Foo
{
public override TFoo Foo { get; }
}

The workaround proposed by Eric Lippert in that linked question is:

class Foo { }

abstract class Bar
{
protected abstract Foo foo { get; }
public Foo Foo => foo;
}

class Bar<TFoo> : Bar where TFoo : Foo
{
protected override Foo foo => this.Foo;
public new TFoo Foo { get { ... } }
}

It has the downside of duplicating not the inheritance hierarchy, but every covariant-simulated property per level of inheritance!

For further reading on how much clutter simulating covariant return types can bring to your code, consider that implementing ICloneable properly implies adding another virtual method per level of inheritance. I'll leave this as my humble plea for that language feature.

Practical uses of generics in .net (Besides the ones in the framework)

I'm using generics every time I need to apply same algorithm / same logic on different types of objects.

Example is generic repository:

public interface IRepository<T> where T : class, IEntity
{
IQueryable<T> GetQuery();
void Update(T entity);
void Insert(T entity);
void Delete(int id);
}

public interface IEntity
{
int Id { get; set; }
}

Do C# Generics Have a Performance Benefit?

There's a significant performance benefit to using generics -- you do away with boxing and unboxing. Compared with developing your own classes, it's a coin toss (with one side of the coin weighted more than the other). Roll your own only if you think you can out-perform the authors of the framework.

Is it good practice for a class to implement itself in a generic interface?

This is more or less the only way in Java to have an interface with methods that refer to the implementing class itself. So, for example, you might write a binary tree node interface:

interface TreeNode<N extends TreeNode<N>> {
N getLeftChild();
N getRightChild();
void setLeftChild(N node);
void setRightChild(N node);
}

and then you have classes like

class TreeNodeWithInt extends TreeNode<TreeNodeWithInt> {
int value;
TreeNodeWithInt leftChild;
TreeNodeWithInt rightChild;
public TreeNodeWithInt getLeftChild() { return leftChild; }
public void setLeftChild(TreeNodeWithInt newLeft) { leftChild = newLeft; }
...
}

If we didn't have the N type parameter, you would be forced to write unsafe code like

class TreeNodeWithInt extends TreeNode {
int value;
TreeNodeWithInt leftChild;
TreeNodeWithInt rightChild;

public void setLeftChild(TreeNode node) {
// note the dangerous cast!
leftChild = (TreeNodeWithInt) node;
}
}

The key issue here is that interfaces aren't allowed to refer to the type of the class that's implementing the interface, when they describe the input and return types of methods. So instead, you include a "self" generic type parameter. It's a relatively common idiom in code that makes extensive use of generics.

As you've correctly identified, it's frequently used with Comparable specifically, because you should only be able to compare objects to other objects of the same type. Indeed, Collections.sort is specified to take a List<T> where T extends Comparable<? super T>, which means that T is comparable to at least other values of type T, but possibly others as well.

(Finally, as you might expect -- since it's the only way to achieve this behavior, it's not "good" or "bad" practice. That said, the goal of the technique is to avoid writing methods that compile without warnings but can end up throwing ClassCastException, which is itself a good practice.)

Benefits of Using Generics in a Base Class that Also Implement the Same Class

Why would you do this? I'm noticing that in practice the extension of
ServiceBase always uses the same class name as T as the one that is
being declared; so there's not really any magic polymorphic benefit
here.

Generics don't exist to create magic polymorphim. It is mainly a way to add constraints on types at compile time in order to reduce clumsy cast and error type at runtime.

In your case, suppose that ServiceBase class is abstract and has a process() method which needs to create at each call a new instance of the concrete class we declare in the parameterized type.

We call this abstract method createService().

Without using generics, we could declare the method like that public abstract ServiceBase createService().

ServiceBase without generics

public abstract class ServiceBase implements Service {

public abstract ServiceBase createService();

@Override
public void process() {
createService().process();
}

}

With this declaration, the concrete class may return any instance of ServiceBase.

For example, the following child class will compile because we are not forced to change the returned type of createService() to the current declared type.

MyService without generics

public class MyService extends ServiceBase {

@Override
public ServiceBase createService() {
return new AnotherService();
}

}

But if I use generics in base class :

ServiceBase with generics

public abstract class ServiceBase<T extends Service> implements Service {

public abstract T createService();

@Override
public void process() {
createService().process();
}

}

The concrete class has no choice, it is forced to change the returned type of createService() with the parameterized type declared.

MyService with generics

public class MyService extends ServiceBase<MyService> {

@Override
public MyService createService() {
return new MyService();
}

}


Related Topics



Leave a reply



Submit