Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Delegate C#

Im trying to figure out how to make a generic delegate returning a generic value. My first non-generic scenario looks like this.

    delegate int FirstDelegate(int i);
    static FirstDelegate method;

    static void Main(string[] args)
    {
        method = ReturnInt;
        int i = method(3);
    }

    static int ReturnInt(int i)
    {
        return i;
    }

No problems here. Everything works fine. However when I make it generic things spin out of control.

    delegate T FirstDelegate<T>(T i);
    static FirstDelegate<T> method; <--

Already here he starts complaining about type or namespace etc etc not found. Anyone have any ideas of how to get this working?

Edit: My real goal is that I have a cache that can contain many different cache objects. And now I want a single method that is generic so I can get all objects through this one. I could make it return object or a baseclass, but then I still would have to cast each object everywhere its used.

Dog / cat example The non-generic parts are working.. the generic.. not so much

class Program
{
    static void Main(string[] args)
    {
        //Clientside
        Cache.method = GetAnimalOnClient;

        //not working
        Cache.methodGeneric = GetAnimalOnClientGeneric;

        var cat = Cache.GetCachedObj(AnimalType.Cat);
        var dog = Cache.GetCachedObj(AnimalType.Dog);

        //Want do
        vad dog = Cache.GetCachedObj<Dog>();
    }

    private static Animal GetAnimalOnClient(AnimalType type)
    {
        if (type == AnimalType.Dog)
            return Cache._Dogs.First();
        else
            return Cache._Cats.First();
    }

    /// <summary>
    /// This is the one I want to use
    /// </summary>
    private static T GetAnimalOnClientGeneric<T>() where T: Animal
    {
        if (typeof(T) == typeof(Cat))
            return Cache._Cats.First() as T;
        return Cache._Dogs.First() as T;
    }
}

public enum AnimalType
{
    Dog,
    Cat
}

public static class Cache
{
    delegate Animal GetCacheObjectDelegate(AnimalType type);
    public static GetCacheObjectDelegate method;

    delegate Animal GetCacheObjectDelegate<T>() where T : Animal;
    public static GetCacheObjectDelegate<T> methodGeneric; //<--Complains here

    public static List<Dog> _Dogs = new List<Dog>();
    public static List<Cat> _Cats = new List<Cat>();

    public static Animal GetCachedObj(AnimalType type)
    {
        return method(type);
    }

    public static T GetCachedObj<T>() where T: Animal
    {
        return methodGeneric<T>(); //NOPE
    }
}

public class Animal
{

}

public class Dog : Animal
{

}

public class Cat : Animal
{

}
like image 359
Evelie Avatar asked Feb 20 '14 12:02

Evelie


2 Answers

You're overcomplicating things.

public static class Cache
{
    private static List<Dog> _dogs = new List<Dog>();
    private static List<Cat> _cats = new List<Cat>();

    public static TAnimal GetCachedObj<TAnimal>() where T: Animal
    {
        if(TAnimal == typeof(Dog))
           return (TAnimal) _dogs.First();
        else if (TAnimal == typeof(Cat))
           return (TAnimal) _cats.First();
        else throw new InvalidOperationException("Invalid generic type argument");
    }
}

But your whole design has a flaw: it breaks the Liskov Substitution Principle.

The LSP states that if T (for example, Cat) is a subtype of Animal, then any instance of Animal can be replaced with T without any surprising effects.

Let me ellaborate. Say that you decide to create a new animal, a Giraffe. Now, if you call GetCachedObj<Giraffe>, you'll get an exception! The code does not work for any subtype of Animal, the LSP does not hold!

Instead you should make the cache class generic, and use a cache instance for every kind of animal

public class Cache<T> where T: Animal
{
    private static List<T> _animals = new List<T>();

    public T GetCachedObj()
    {
        return _animals.First();
    }
}

var dogsCache = new Cache<Dog>();
Dog dog = dogsCache.GetCachedObj();

var catsCache = new Cache<Cat>();
Cat cat = catsCache.GetCachedObj();

This will always work for any kind of animal.

Note: I believe Cache shouldn't be static. Instead, you can use the Singleton pattern to have one single cache instance across the application (per animal type), or use Dependency Injection (with a framework such as Castle Windsor) to inject a cache into every client.


old answer

You either bind the method's generic type argument to a specific type at declaration-time (as @Sean mentioned), or you make the enclosing type generic as well.

public class MyClass<T>
{
    public FirstDelegate<T> Method(){...}
}

You can also leave T unbound (without making the enclosing type generic), but you'll have to declare T after the method name, like so:

public FirstDelegate<T> Method<T>(){...}

Either way, at some point in time, T will be bound to a specific type. In this case, T will be bound when you create an instance of MyClass (i.e., new MyClass<int>), like you would do with a List<T>.

like image 118
dcastro Avatar answered Sep 17 '22 06:09

dcastro


You need to specify the type when declaring method :

static FirstDelegate<int> method;
like image 28
Sean Avatar answered Sep 20 '22 06:09

Sean