Why use "List<T> where T : SomeType" if you know the type

Viewed 148

I'm trying to understand generic constraints. Sadly I've got no reason to use them at the moment but trying my best to come up with situations so I can play with it and understand how/when they will help.

I've run into an issue. To me, these 2 are the same

public List<T> Get<T>() where T : DemoClassTwo
public List<DemoClassTwo> Get()

Both will return a List, and have to be of type DemoClassTwo and so I don't see why I'd use the method with that constraint.

Some actual code to demonstrate this

static void Main(string[] args)
{
    var dco = new DemoClassOne();
    dco.Get().ForEach((a) => { Console.WriteLine(a.Id); });
    dco.Get<DemoClassTwo>().ForEach((a) => { Console.WriteLine(a.Id); });
    Console.ReadKey();
}

And the supporting classes

public class DemoClassOne
{
    public List<T> Get<T>() where T : DemoClassTwo, new()
    {
        var result = new List<T>();
        var t = new T();
        t.Id = 1;
        result.Add(t);
        return result;
    }

    public List<DemoClassTwo> Get()
    {
        var result = new List<DemoClassTwo>();
        var d = new DemoClassTwo();
        d.Id = 1;
        result.Add(d);
        return result;    //Code here is pretty much identical to the other Get method
    }
}

public class DemoClassTwo
{
    public int Id { get; set; }
    public DemoClassTwo()
    {}
}

Have I just found a situation where the 2 are the same or am I missing something in the way it works.

This question is similar to In C#, why use Where T : ConcreteClass? but different as I'm not asking why to use it, but why it is different.

4 Answers

I don't see why I'd use the method with that constraint.

Then just don't.

You use generics when you want the same code to operate on different types, and you use constraints when you want to constrain the types you can use with that generic method. That's all there is to it.

Your code knows what it wants to return, namely a List<DemoClassTwo>, so it's not really useful to let the caller specify another type using the generic overload.

It might make sense in some scenarios, but you're asking us to make up an example where it does make sense, and that makes no sense.

Given a sensible example, a context where you want to query entities, you could create a filter on a base entitiy:

public abstract class BaseEntity
{
    public DateTime? DeletedOn { get; set; }
}

public class NotDeletedFilter<TEntity> 
    where TEntity : BaseEntity
{
    public IQueryable<TEntity> Filter(IQueryable<TEntity> entities)
    {
        return entities.Where(e => e.DeletedOn == null);
    }
}

Now you can apply this filter to any queryable entity collection whose entity inherits from BaseEntity.

If instead of the class BaseEntity you have an interface IBaseEntity, you'd better use that. But sometimes you don't have an existing interface and you can't modify the types to be used with your generic method. In that case you'd have to use a shared base class.

The two methods are not identical. I think the second without generics is just an example, to show the behavior of the generic one...

Anyway :

public List<T> Get<T>() where T : DemoClassTwo, new()

means the method expects T to be of type (or subtype) DemoClassTwo, and having a parameterless public constructor.

public List<DemoClassTwo> Get()

means the method will return a List<DemoClassTwo>.

Imagine you have a class DemoClassThree

public class DemoClassThree : DemoClassTwo
{
    public DemoClassThree() {
       Console.Write("New instance created !");
    }
}

You can then call the generic Get method like this, and get a List<DemoClassThree>:

var demoClassOneInstance = new DemoClassOne()
List<DemoClassThree> result =  demoClassOneInstance.Get<DemoClassThree>()

And you will get on your console :

New instance created !

But If you try to do :

public class DemoClassFour
{
    public DemoClassFour() {
       Console.Write("New instance of DemoClassFour created !");
    }
}

List<DemoClassFour> result =  demoClassOneInstance.Get<DemoClassFour>()

...you will get a compilation error, because DemoClassFour does not inherit DemoClassTwo.

As Icepickle said, you are only limited by your imagination... or at least real use case :)

You can constrain a Generic Type Parameter to only contain certain Base Classes or Interfaces. If I extend your example and make some assumptions. DemoClassTwo Implies you probably have DemoClassOne

Now by extension, you could also have either a DemoClassBase or an IDemoClass

In this situation, you may want to store any type which implements the IDemoClass interface.

With restrictions on the Type a generic can use, you can ensure that a certain subset of functionality on your items may exist, across multiple different implementations of that interface.

Likewise with a DemoClassBase You may not care what aother additional methods have been defined on classes that extend DemoClassBase, but the code creating an instance of your class using Generic Type Parameters does. Therefore you can specify the DemoBaseClass as being something the elements must extend. This ensures the code knows it has access to at least that set of methods defined on the Base Class.

I think the totally best explanation is as Lasse Vågsæther Karlsen said the

public List<T> Get<T>() where T : DemoClassTwo

can return DemoClassTwo and every of it`s subclasses, but

public List<DemoClassTwo> Get() can return only DemoClassTwo

Related