Evelie
Evelie

Reputation: 3059

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
{

}

Upvotes: 6

Views: 18144

Answers (5)

Chen Hao
Chen Hao

Reputation: 136

You can declare a Dictionary in the Cache class, use the Animal class type as the key and List<Animal> as the value.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main(string[] args)
    {
        Cache.AddCacheObj<Dog>(new Dog());
        Cache.AddCacheObj<Cat>(new Cat());

        var cat = Cache.GetCachedObj<Dog>();
        Console.WriteLine("Cat: {0}", cat);

        var dog = Cache.GetCachedObj<Cat>();
        Console.WriteLine("Dog: {0}", dog);     
    }
}

public static class Cache
{
    static Dictionary<Type, List<Animal>> dict = new Dictionary<Type, List<Animal>>();

    public static T GetCachedObj<T>() where T: Animal
    {
        List<Animal> list;
        if (dict.TryGetValue(typeof(T), out list))
        {
            return list.FirstOrDefault() as T;
        }
        return null;
    }

    public static void AddCacheObj<T>(T obj) where T: Animal
    {
        List<Animal> list;
        if (!dict.TryGetValue(typeof(T), out list))
        {
            list = new List<Animal>();
            dict[typeof(T)] = list;
        }
        list.Add(obj);
    }
}

public class Animal
{
    public override string ToString()
    {
        return "This is a " + this.GetType().ToString();
    }
}

public class Dog : Animal
{

}

public class Cat : Animal
{

}

The output:

Cat: This is a Dog
Dog: This is a Cat

You can check out the demo code here: https://dotnetfiddle.net/FYkCw7

Upvotes: 0

Lightman
Lightman

Reputation: 1265

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.

There is no need to use delegates in this particular problem, because LINQ allows selecting objects by type from IEnumerable:

public class AnimalCache

    private readonly Animals as new HashSet(of Animal)

    public function Add(Animal as Animal) as AnimalCache
        Animals.Add(Animal)
        return me
    end function

    public function GetAnimals(of AnimalType as Animal) as IEnumerable(of AnimalType)
        return Animals.OfType(of AnimalType)
    end function
end class

Usage:

dim Cache = new AnimalCache().
    Add(new Cat).Add(new Dog).Add(new Cat)

dim CachedCats = Cache.GetAnimals(of Cat)
dim CachedDogs = Cache.GetAnimals(of Dog)

Note that if a Giraffe class is added into the class hierarchy, AnimalCache will still work:

public class Giraffe 
    inherits Animal

end class

dim Cache = new AnimalCache().
    Add(new Cat).Add(new Dog).Add(new Giraffe)

dim CachedGiraffes = Cache.GetAnimals(of Giraffe)

Upvotes: 0

Cob4in
Cob4in

Reputation: 1

If You have an instance of the object You want to get default value, You can use this:

public static T GetDefault<T>(T par)
    {
        return default(T);
    }

For example:

System.Drawing.Point p = new Point();
System.Drawing.Point defaultPoint = GetDefault(p);

Upvotes: 0

dcastro
dcastro

Reputation: 68660

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>.

Upvotes: 6

Sean
Sean

Reputation: 62472

You need to specify the type when declaring method :

static FirstDelegate<int> method;

Upvotes: 4

Related Questions