2015-07-14 2 views
2

У меня есть общий интерфейс и куча классов, которые его реализуют. Вот очень упрощенный пример:Как вызвать общий метод со слабо типизированным параметром

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); 
    } 
} 

У меня также есть словарь этих классов с типом родового аргумента в качестве ключа (я на самом деле использовать отражение для его заполнения):

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

Теперь я Recive экземпляр List<object> в виде ввода, который содержит кучу параметров произвольного типа. Для каждого параметра я хочу выбрать объект, который реализует соответствующий интерфейс и вызывает метод печати. Я не уверен, как это реализовать.

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); 
} 

Я попытался

  1. Отражение. Это работает, но хорошо ... его отражение. Я ищу другие способы сделать это.

    private void Print(object printer, object arg) 
    { 
        printer.GetType().GetMethod("Print").Invoke(printer, new[] {arg}); 
    } 
    
  2. Динамический. Гораздо чище, чем отражение, и обычно быстрее. Но по какой-то причине он генерирует исключение, если я использую его с типом, который равен private относительно выполнения сборки. Является ли это известным ограничением динамических объектов?

    private void Print(dynamic printer, dynamic arg) 
    { 
        printer.Print(arg); 
    } 
    
  3. Делегат. Я пытался использовать метод CreateDelegate для создания своего рода делегата с слабым типом от MethodInfo, но я полностью не справился с этим. Возможно ли это?

ответ

1

Я забыл поделиться решение, которое я в конечном итоге использовал. Он использует два факта:

  1. Вы можете легко отлитые в T внутри Generic<T> класса.
  2. Вы можете легко (кашельCreateDelegateкашель) построить Generic<T> класс, используя typeof(T) переменную.

LINQPad образец:

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"); 
} 
-1

Возможно, это поможет?

private void Print<T, TArgs>(T printer, TArgs arg) where T : IPrinter<TArgs> 
{ 
    printer.Print(arg); 
} 
+0

Я думаю, что это не будет компилироваться. У меня также нет универсального интерфейса «IPrinter». –

+0

Woops did not замечают это, как насчет сейчас? – maksymiuk

+0

У меня есть два 'объекта' как вход (см. Цикл 'foreach' в моем вопросе). Как я могу назвать ваш метод? –

1

Вы уже знаете, что метод с этой подписью: void GenericMethod<T>(T arg) будет переведен на что-то вроде: void __GenericMethod(Type typeArg1, object argument) после первого вызова для типа класса аргумента (для структур вы будете генерировать для каждой структуры).

Подумайте об этом. Зачем нам вообще нужны дженерики? Почему бы не просто:

interface IPrinter 
{ 
    void Print(object args); 
} 

Или даже ...

interface IAnything 
{ 
    void Do(object args); 
} 

Ответ на: время компиляции особенность C# - безопасность типов. Нам это нужно, чтобы избежать случайного объединения несовместимых компонентов. Вы не хотите, чтобы ваш IntPrinter получил string как-то. К счастью, если вы получите исключение, но что, если нет?

Итак, эта функция хороша и помогает нам обнаруживать ошибку во время компиляции. Но что вы делаете, собирая свой словарь Printers? Вы теряете безопасность. Если вы допустите ошибку с типами где-нибудь, вы испытаете исключение или другое странное поведение вашей программы во время выполнения.

Я хотел бы предложить вам два варианта:

  • сделать IPrinter не типобезопасен
  • держать его родовое, но не теряют безопасность типов при использовании его
+0

Thx. Я не хочу, чтобы интерфейс был неэквивалентным из-за причины, о которой вы упоминали: мне придется передать и каким-то образом обработать аргументы 'string' внутри объектов, которые не должны иметь дело со строками (такими как' IntPrinter') , И пока, к сожалению, я не вижу, как я могу сохранить безопасность типа. –

+0

IL поддерживает generics изначально, поэтому метод будет скомпилирован, как показано на C#. Среда выполнения записывала бы разностную подпись для каждого другого типа, который вы передаете в общий, но ссылка на метод будет похожа на 'void GenericMethod (int arg)', а не 'void GenericMethod (Тип type, object arg)' –

Смежные вопросы