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.
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 ;-)
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);
}
}
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