Simon Rigby
Simon Rigby

Reputation: 1786

Restrict types a generic subclass method can accept in c#

I apologise if the title is ambiguous or if this is a duplicate. I've spent today reading a ton on generics and looking for a similar situation to no avail.

I'm writing a little game engine. One aspect of this is the ability for a 'unit' to carry resources. Some units can carry any resource, some are restricted to carrying certain specialised resources. To this end I have the following structure:

The Resource base class:

public abstract class Resource
{
    private int _count;

    public int Count
    {
        get { return _count; }
        set { _count = value; }
    }

    public Resource(int resourceCount)
    {
        _count = resourceCount;
    }
}

then a Resource specialisation Wood:

public class Wood : Resource
{
    public Wood(int resourceCount) : base(resourceCount)
    {
    }
}

I then have my generic ResourceStore:

public class ResourceStore<T> : IResourceStore where T : Resource
{
    private List<Resource> _store;

    public IEnumerable<Resource> Store { get { return _store; } } 

    public void AddResource(Resource resource)
    {
        _store = new List<Resource>();
        _store.Add(resource);
    }
}

and finally a specialised store WoodStore where the AddResource method should only accept Wood:

public class WoodStore : ResourceStore<Wood>
{

}

For completeness the interface IResourceStore which is implemented by anything that can act as a Resource Store:

public interface IResourceStore
{
    void AddResource(Resource resource);
}

If I run a little console application and do the following I would have expected an error when trying to add Wheat to a WoodStore. But it doesn't and in fact the output shows that the WoodStore now contains Wood and Wheat. This is the console app:

class Program
{
    static void Main(string[] args)
    {
        WoodStore store = new WoodStore();
        store.AddResource(new Wood(10));
        store.AddResource(new Wheat(5));

        foreach (Resource resource in store.Store)
        {
            Console.WriteLine("{0} {1}", resource.GetType(), resource.Count);
        }

        Console.ReadLine();
    }
}

Finally here is Wheat just for completeness although there is nothing special about it:

public class Wheat : Resource
{
    public Wheat(int resourceCount) : base(resourceCount)
    {
    }
}

I'm obviously barking up the wrong tree on this one, and would appreciate any help as to how I would go about restricting WoodStore to only accept Wood. Ultimately there will be a lot of different stores that would have certain restrictions and I'm looking for a way to handle this generically.

Many thanks.

Upvotes: 3

Views: 268

Answers (2)

weston
weston

Reputation: 54781

Your interface needs to be generic and you need to use T in the backing store, also the new List in AddResource is not what you want I bet:

public interface IResourceStore<T> where T : Resource
{
    void AddResource(T resource);
}

public class ResourceStore<T> : IResourceStore<T> where T : Resource
{
    private List<T> _store = new List<T>();

    public IEnumerable<T> Store { get { return _store; } } 

    public void AddResource(T resource)
    {
        //_store = new List<T>(); //Do you really want to create a new list every time you call AddResource?
        _store.Add(resource);
    }
}

Upvotes: 4

Hoghweed
Hoghweed

Reputation: 1948

First of all, as you said in the beginning, the title is not so clear related to the question content.

The first, simple and basic answer to ha have multiple stores each with restrictions is to use the generic filter even in the methods which permit to operate on resources:

public class ResourceStore<T> : IResourceStore where T : Resource 
{ 
         private List<T> _store;

    public IEnumerable<T> Store { get { return _store; } } 

    public void AddResource(T resource)
    {
        _store = new List<T>();
        _store.Add(resource);
    }

    void IResourceStore.AddResource(Resource resource)
    {
        //possible excepion here for type mismach!
        AddResource(resource as T);
    }
}

if you note there's another method without explicit visibility application, the method is the explicit IResourceStore interface implementation, this is needed, but I can suggest is not a good design solution.

First of all if the concept of abstract store is generic, even the interface should be generic if not explicitly needed by your needs, so the interface should be as the same generic of the abstract class with generic restrictions:

public interface IResourceStore<T> 
   where T : Resource
{
   void AddResource(T resource);
}

in the same way the enumerable support should be included in the interface so the abstract class will support it. So the final code should be similar to this:

public class ResourceStore<T> : IResourceStore<T> where T : Resource
{
    private List<T> _store;

    public IEnumerable<T> Store { get { return _store; } }

    public void AddResource(T resource)
    {
        _store = new List<T> {resource};
    }

    public IEnumerator<T> GetEnumerator()
    {
        return Store.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public interface IResourceStore<T> : IEnumerable<T>
   where T : Resource
{
        void AddResource(T resource);
}

public abstract class Resource
{
    public int Count { get; set; }

    protected Resource(int resourceCount)
    {
        Count = resourceCount;
    }
}

Hope this helps. I also applied some little code optimization.

Upvotes: 0

Related Questions