How to invoke a generic method with weak-typed parameter

+2 votes
asked Jul 14, 2015 by nikita-b

I've got a generic interface and bunch of classes which implement it. Here is a greatly simplified example:

interface IPrinter<in T>
{
    void Print(T args);
}

class IntPrinter : IPrinter<int>
{
    public void Print(int args)
    {
        Console.WriteLine("This is int: " +args);
    }
}

class StringPrinter : IPrinter<string>
{
    public void Print(string args)
    {
        Console.WriteLine("This is string: " + args);
    }
}

I also have a dictionary of those classes with generic argument's type as a key (I actually use reflection to populate it):

private readonly Dictionary<Type, object> Printers 
    = new Dictionary<Type, object>
        {
            {typeof (int), new IntPrinter()},
            {typeof (string), new StringPrinter()}
        };

Now I recive an instance of List<object> as an input, which contains a bunch of parameters of arbitrary type. For each parameter I want to pick the object, which implement an appropriate interface and call the print method. I am not sure how to implement that.

var args = new List<object> {1, "2"};
foreach (var arg in args)
{
    var printer = Printers[arg.GetType()];
    //how do I implement this method so it calls ((IPrinter<T>)printer).Print((T)arg)?
    Print(printer, arg); 
}

I have tried

  1. Reflection. It works, but well... its reflection. I am looking for other ways to do it.

    private void Print(object printer, object arg)
    {
        printer.GetType().GetMethod("Print").Invoke(printer, new[] {arg});
    }
    
  2. Dynamic. A lot cleaner than reflection and usually faster. But for some reason it throws an exception if I use it with a type, which is private relative to executing assembly. Is this a known limitation of dynamic objects?

    private void Print(dynamic printer, dynamic arg)
    {
        printer.Print(arg);
    }
    
  3. Delegate. I was trying to use CreateDelegate method to create a some kind of weak-typed delegate from MethodInfo but i completely failed at that. Is it even possible?

3 Answers

–1 vote
answered Jul 14, 2015 by maksymiuk

Would this help?

private void Print<T, TArgs>(T printer, TArgs arg) where T : IPrinter<TArgs>
{
    printer.Print(arg);
}
+1 vote
answered Jul 14, 2015 by astef

You might already know that method with this signature: void GenericMethod<T>(T arg) would be translated to something like: void __GenericMethod(Type typeArg1, object argument) after first call for a class type argument (for structures you'll generate one for each struct).

Think about it. Why do we need generics at all? Why not to simply have:

interface IPrinter
{
    void Print(object args);
}

Or even...

interface IAnything
{
    void Do(object args);
}

The answer is: compile-time feature of C# - type-safety. We need it to avoid combining not compatible components accidentally. You don't want your IntPrinter to receive a string somehow. Lucky if you'll get an exception, but what if not?

So, using this feature is good and helps us to detect error at compile-time. But what are you doing by collecting your Printers dictionary? You're loosing type-safety. If you'll make a mistake with types somewhere, you'll experience an exception or another strange behaviour of your program at runtime.

I would propose you two alternatives:

  • make IPrinter not type safe
  • keep it generic, but don't loose type-safety while using it
+1 vote
answered Jun 13, 2017 by nikita-b

I forgot to share the solution which I ended up using. It utilizes two facts:

  1. You can easily cast to T inside Generic<T> class.
  2. You can easily (coughCreateDelegatecough) construct Generic<T> class using typeof(T) variable.

LinqPad sample:

interface IPrinter<in T>
{
    void Print(T args);
}

class IntPrinter : IPrinter<int>
{
    public void Print(int args)
    {
        Console.WriteLine("This is int: " +args);
    }
}

class StringPrinter : IPrinter<string>
{
    public void Print(string args)
    {
        Console.WriteLine("This is string: " + args);
    }
}

interface IPrintInvoker
{
    void Print(object printer, object args);
}

class PrintInvoker<T> : IPrintInvoker
{
    public void Print(object printer, object args)
    {
         ((IPrinter<T>)printer).Print((T)args);
    }
}

private readonly Dictionary<Type, IPrintInvoker> Invokers = new Dictionary<Type, IPrintInvoker>();

private void Print(object printer, object arg)
{
     var type = arg.GetType();
    IPrintInvoker invoker;
    if (!Invokers.TryGetValue(type, out invoker))
    {
        var invokerType = typeof(PrintInvoker<>).MakeGenericType(type);
        Invokers[type] = invoker = (IPrintInvoker)Activator.CreateInstance(invokerType );
    }
    invoker.Print(printer, arg);
}

void Main()
{
   var printer1 = new IntPrinter();
   var printer2 = new StringPrinter();
   Print(printer1, 1);
   Print(printer1, 2);
   Print(printer2, "asfasfasf");
}
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...