Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of constraining a type to an interface?

What is the purpose of allowing the following?

class A<T> where T : IFoo {
    private T t;
    A(T t) { this.t = t; }
    /* etc */
}

How is this meaningfully different from just declaring A to require an IFoo wherever it needs one?

class A {
    private IFoo foo;
    A(IFoo foo) { this.foo = foo; }
    /* etc */
}

The only difference that I can see is that in the first case I'm guaranteed both that A<T> will always be instantiated with a T that implements IFoo and that all of the objects in A will be of the same base type. But for the life of me I can't figure out why I'd need such a constraint.

like image 895
JSBձոգչ Avatar asked Dec 04 '10 16:12

JSBձոգչ


2 Answers

The main difference in your 2 examples is that in class A whenever you define a variable as T you can use all properties/functions on that variable that are also defined in IFoo.

However in class B the IFoo is just a name for the generic type parameter and thus whenever you declare a variable inside the class as IFoo you can only use it as if it's an object type.

for example if

public interface IFoo
{
   int Value { get; set; }
}

then you can do this in class A

class A<T> where T : IFoo 
{
     public void DoSomething(T value)
     {
          value.Value++;
     }
}

While if you'd try the same in class B you'll get a compiler error that the type IFoo does not contain a property Value or something similar. The reason is that the <IFoo> in class B is just a name and has no relation to the interface, you could've called it anything you like.

Update:

class B {
    private IFoo foo;
    B(IFoo foo) { this.foo = foo; }
    /* etc */
}

This construct is indeed basically the same, except when you expose IFoo back to the outside again, Consider the following property in both classes

class A:

public T Foo { get { return foo; }}

class B:

public IFoo Foo { get { return foo; }}

Now consider you initialized both classes with a class C which is defined as

public class FooClass : IFoo
{
    public int Value { get; set; }
    public int SomeOtherValue { get; set; }
}

then consider 2 variables defined as

var foo = new FooClass();
var a = new A<FooClass>(foo);
var b = new B(foo);

now to set the SomeOtherValue using a you can do

a.Foo.SomeOtherValue = 2;

while for b you have to do

((FooClass)b.Foo).SomeOtherValue = 2;

Hope that makes sense ;-)

like image 197
Doggett Avatar answered Nov 14 '22 23:11

Doggett


Edit: At first I thought you were daft: the first example surely wouldn't even compile. Only after trying it myself (and seeing that it does compile) did I realize what Doggett already pointed out: your class B<IFoo> example actually has no relationship to the IFoo interface; it's just a generic type whose type parameter happens to be called IFoo.

Maybe you were aware of this, and you really were asking, "Why would I want to constrain a generic type parameter at all?" If that's the case then I think the other answers have addressed this to some extent. But it sounded like you were asking, "Why would I define my type like that, instead of like this (since they are practically the same)?" The answer to this is, quite simply: they are not the same.


Now, here's another question—one you didn't ask, but the one I originally set out to answer ;)

Why define a type like this:

class A<T> where T : IFoo
{
    T GetFoo();
}

...instead of this?

class A
{
    IFoo GetFoo();
}

Here's one reason that springs to my mind (because it resembles scenarios I've dealt with in the past): you are designing not one class, but a small hierarchy of classes, and IFoo is simply the "base line" interface all of your classes will require, while some may leverage specific implementations, or more derived interfaces.

Here's a dumb example:

class SortedListBase<T, TList> where TList : IList<T>, new()
{
    protected TList _list = new TList();

    // Here's a method I can provide using any IList<T> implementation.
    public T this[int index]
    {
        get { return _list[index]; }
    }

    // Here's one way I can ensure the list is always sorted. Better ways
    // might be available for certain IList<T> implementations...
    public virtual void Add(T item)
    {
        IComparer<T> comparer = Comparer<T>.Default;
        for (int i = 0; i < _list.Count; ++i)
        {
            if (comparer.Compare(item, _list[i]) < 0)
            {
                _list.Insert(i, item);
                return;
            }
        }

        _list.Add(item);
    }
}

class SortedList<T> : SortedListBase<T, List<T>>
{
    // Here is a smarter implementation, dependent on List<T>'s
    // BinarySearch method. Note that this implementation would not
    // be possible (or anyway, would be less direct) if SortedListBase's
    // _list member were simply defined as IList<T>.
    public override void Add(T item)
    {
        int insertionIndex = _list.BinarySearch(item);

        if (insertionIndex < 0)
        {
            insertionIndex = ~insertionIndex;
        }

        _list.Insert(insertionIndex, item);
    }
}
like image 32
Dan Tao Avatar answered Nov 14 '22 23:11

Dan Tao