Является ли использование динамической считается плохой практикой?

В С# кто-то может сделать:

MyClass myInstance        = new MyClass();
dynamic mydynamicInstance = myInstance;

И затем вызовите метод, например:

//This method takes a MyClass argument and does something.
Caller.InvokeMethod(myDynamicInstance);

Теперь это приведет к определению типа myInstance во время выполнения, и, если оно действительно, Caller.InvokeMethod будет вызвано нормально.

Теперь, мой вопрос заключается в том, что это считается плохой практикой для использования dynamic, особенно в следующих случаях:

1) InvokeMethod создает экземпляр другого экземпляра типа myDynamicInstance, используя отражение внутри.

2) Существует абстрактный базовый класс MyBaseClass и ряд подклассов, включая MyBaseClass. Если у нас есть несколько перегруженных методов InvokeMethod для всех этих производных классов, можно ли использовать его для того, чтобы во время выполнения определять тип, а затем правильный вызов посредством перегрузки метода (или позднего связывания при вызове метода этого класса)?:

public abstract class MyBaseClass         {/*...*/}
public class MyClass        : MyBaseClass {/*...*/}
public class MyAnotherClass : MyBaseClass {/*...*/}

MyBaseClass myBaseClassRef = new MyClass();
dynamic myDynamicInstance  = myBaseClassRef;

Caller.InvokeMethod(myDynamicInstance);

Ответ 1

Короткий ответ - ДА, использовать динамическую практику - плохая практика.

Почему?

динамическое ключевое слово относится к типу позднего связывания, что означает, что система будет проверять тип только во время выполнения, а не во время компиляции. Затем это означает, что пользователь, а не программист, остается обнаружить потенциальную ошибку. Ошибка может быть исключение MissingMethodException, но это также может быть не подразумеваемый вызов существующего метода с плохим поведением. Представьте себе вызов метода, который заканчивается при вычислении плохой цены или при вычислении плохого уровня кислорода.

Вообще говоря, проверка типов помогает получить детерминированные вычисления, и поэтому, когда вы можете, вы должны их использовать. Вот вопрос о недостатках динамического.

Однако динамика может быть полезна...

  • Взаимодействие с COM как с Office
  • Взаимодействие с языками, в которых динамические типы являются частью языка (IronPython, IronRuby), в качестве динамического, чтобы помочь портировать их в .Net.
  • Может заменить сложный код отражения с низкой ценой, элегантный код (однако, в зависимости от случая, вы все равно должны прорабатывать оба подхода, чтобы проверить, какой из них наиболее уместен с точки зрения производительности и времени компиляции чеки).

Кодовая база развивается на протяжении всего жизненного цикла приложения, и даже если динамика выглядит нормально, он устанавливает прецедент, который может подразумевать увеличение использования динамического ключевого слова вашей командой. Это может привести к увеличению расходы на обслуживание (в случае, если вышеуказанная подпись развивается, вы можете заметить ее слишком поздно). Конечно, вы могли бы полагайтесь на модульные тесты, не регрессивные человеческие тесты и так далее. Но когда вам нужно выбирать между человеческой дисциплиной качество и автоматически проверяется по качеству компьютера, выберите позже. Это менее подвержено ошибкам.

В вашем случае...

В вашем случае, похоже, вы можете использовать общую схему наследования (первый ниже и тот, который вы укажете в своем вопросе), поскольку dynamic не даст вам дополнительных преимуществ (это просто обойдется вам в большей вычислительной мощности и сделает вас риск будущих потенциальных ошибок).

Это зависит от того, можете ли вы изменить код иерархии MyClass и/или Caller.InvokeMethod.

Позвольте перечислить различные возможные альтернативы динамическому...

  • Скомпилированный проверенный тип альтернативы вызову метода динамического ключевого слова:

Наиболее распространенным является использование виртуального вызова интерфейса, подобного этому экземпляру. InvokeMethod() с наследованием, вызывающим правильную реализацию.

public interface IInvoker : { void InvokeMethod(); }
public abstract class MyBaseClass : IInvoker { public abstract void InvokeMethod(); }
public class MyAnotherClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }
public class MyClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }

Еще немного менее результативным является использование методов расширения

public static class InvokerEx:
{
    public static void Invoke(this MyAnotherClass c) { /* Do something */ } }
    public static void Invoke(this MyClass c) { /* Do something */ } }
}

Если существует несколько "посетителей" иерархии MyBaseClass, вы можете использовать шаблон посетителя:

public interface IVisitor 
{
    void Visit(this MyAnotherClass c);
    void Visit(this MyClass c);
}

public abstract class MyBaseClass : IInvoker { public abstract void Accept(IVisitor visitor); }
public class MyAnotherClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }
public class MyClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }

Другие варианты, но не очень полезные здесь (Общий метод), но интересный для сравнения производительности:

public void InvokeMethod<T>(T instance) where T : IInvoker { return instance.InvokeMethod(); }
  • Динамическая альтернатива вызову метода динамического ключевого слова:

Если вам нужно вызвать метод, не известный во время компиляции, я добавил ниже различные методы, которые вы могли бы использовать, и обновили результаты работы:

MethodInfo.CreateDelegate

        _method = typeof (T).GetMethod("InvokeMethod");
        _func = (Func<T, int>)_method.CreateDelegate(typeof(Func<T, int>));

Примечание. Для того, чтобы избежать вызова DynamicInvoke (как правило, медленнее), необходимо использовать Cast to Func.

DynamicMethod и ILGenerator.Emit

Фактически он создает полный вызов с нуля, он является наиболее гибким, но у вас должен быть какой-то ассемблерный фон, чтобы полностью его оценить.

        _dynamicMethod = new DynamicMethod("InvokeMethod", typeof (int), new []{typeof(T)}, GetType().Module);
        ILGenerator il = _dynamicMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, _method);
        il.Emit(OpCodes.Ret);
        _func = (Func<T, int>) _dynamicMethod.CreateDelegate(typeof (Func<T, int>));

Выражение Linq

Он похож на DynamicMethod, однако вы не контролируете генерируемый IL. Хотя, это действительно более читаемо.

        _method = typeof (T).GetMethod("InvokeMethod");
        var instanceParameter = Expression.Parameter(typeof (T), "instance");
        var call = Expression.Call(instanceParameter, _method);
        _delegate = Expression.Lambda<Func<T, int>>(call, instanceParameter).Compile();
        _func = (Func<T, int>) _delegate;

MethodInfo.Invoke

Последний, но не менее важный, стандартный вызов отражения. Однако, даже если с ним легко справиться, не используйте его, так как это действительно плохой исполнитель (посмотрите результаты теста). Предпочитаете CreateDelegate, который действительно быстрее.

        _method = typeof (T).GetMethod("InvokeMethod");
        return (int)_method.Invoke(instance, _emptyParameters);

Код тестового теста можно найти на GitHub.

Сравнение различных методов, чтобы получить порядок величины (для 10 миллионов звонков) (.NET Framework 4.5):

For Class standard call:
Elapsed: 00:00:00.0532945
Call/ms: 188679
For MethodInfo.CreateDelegate call:
Elapsed: 00:00:00.1131495
Call/ms: 88495
For Keyword dynamic call:
Elapsed: 00:00:00.3805229
Call/ms: 26315
For DynamicMethod.Emit call:
Elapsed: 00:00:00.1152792
Call/ms: 86956
For Linq Expression call:
Elapsed: 00:00:00.3158967
Call/ms: 31746
For Extension Method call:
Elapsed: 00:00:00.0637817
Call/ms: 158730
For Generic Method call:
Elapsed: 00:00:00.0772658
Call/ms: 129870
For Interface virtual call:
Elapsed: 00:00:00.0778103
Call/ms: 129870
For MethodInfo Invoke call:
Elapsed: 00:00:05.3104416
Call/ms: 1883
For Visitor Accept/Visit call:
Elapsed: 00:00:00.1384779
Call/ms: 72463
 == SUMMARY ==
Class standard call: 1
Extension Method call : 1,19
Generic Method call : 1,45
Interface virtual call : 1,45
MethodInfo.CreateDelegate call : 2,13
DynamicMethod.Emit call : 2,17
Visitor Accept/Visit call : 2,60
Linq Expression call : 5,94
Keyword dynamic call : 7,17
MethodInfo Invoke call : 100,19

EDIT:

Таким образом, по сравнению с шаблоном Visitor динамическая отправка примерно в в 3 раза медленнее. Он может быть приемлемым для некоторых приложений, поскольку он может удалить громоздкий код. Вам всегда нужно выбирать.
Просто имейте в виду все недостатки.


РЕДАКТИРОВАТЬ: (в качестве ответа на несколько преимуществ отправки)

Используя модное название шаблона, например "множественная отправка", и просто укажите, что он более чистый, потому что он использует меньше кода, не делает его дополнительным преимуществом IMHO. Если вы хотите написать модный код или не заботитесь о типе безопасности и стабильности производства, там уже много языка, предлагающего полнофункциональную динамическую типизацию. Я вижу введение ключевого слова dynamic в С# как способ закрыть разрыв между сильным типизированным семейством языков и не так сильно набирать другие языки. Это не значит, что вы должны изменить способ разработки и поместить проверки типов в корзину.

ОБНОВЛЕНИЕ: 2016/11/08 (.NET Framework 4.6.1)

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

Class standard call: 1
Extension Method call : 1,19
Interface virtual call : 1,46
Generic Method call : 1,54
DynamicMethod.Emit call : 2,07
MethodInfo.CreateDelegate call : 2,13
Visitor Accept/Visit call : 2,64
Linq Expression call : 5,55
Keyword dynamic call : 6,70
MethodInfo Invoke call : 102,96

Ответ 2

Я не полностью согласен с Фабиеном, что он не дает вам дополнительных преимуществ. То, что он решает с шаблоном посетителя, называется множественной диспетчеризацией, а динамическое может также обеспечить чистое решение. Конечно, вы должны знать о последствиях, которые Фабьен упомянул как производительность, проверка статического типа...

public abstract class MyBaseClass
{
}

public class MyClass : MyBaseClass
{
}
public class MyAnotherClass : MyBaseClass
{
}

public class ClassThatIsUsingBaseClass
{
    public static void PrintName(MyBaseClass baseClass)
    {
        Console.WriteLine("MyBaseClass");
    }

    public static void PrintName(MyClass baseClass)
    {
        Console.WriteLine("MyClass");
    }

    public static void PrintName(MyAnotherClass baseClass)
    {
        Console.WriteLine("MyAnotherClass");
    }

    public static void PrintNameMultiDispatch(MyBaseClass baseClass)
    {
        ClassThatIsUsingBaseClass.PrintName((dynamic)baseClass);
    }
}

И использование

static void Main(string[] args)
{
    MyBaseClass myClass = new MyClass();
    MyBaseClass myAnotherClass = new MyAnotherClass();
    ClassThatIsUsingBaseClass.PrintName(myClass);
    ClassThatIsUsingBaseClass.PrintName(myAnotherClass);
    ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myClass);
    ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myAnotherClass);

    Console.ReadLine();
}

Выход

MyBaseClass
MyBaseClass
MyClass
MyAnotherClass

Найдите "множественную отправку" и "множественную отправку С#" для получения дополнительной информации.

Ответ 3

На этот вопрос ответили в 2015 году, а сегодня в 2019 году, с помощью шаблонов, которые мы видим в JavaScript и Typescript, действительно есть веские причины для использования динамических; однако это требует от разработчика осторожности.

    private (Boolean Valid, dynamic Result) ValidateID(int ID)
    {
        var person = _store.Persons.FirstOrDefault(person => person.ID == ID);
        if (person == null)
        {
            string message = $"The person id {ID} does not exist, please try again.";
            return (false, message);
        }
        return (true, person);

    }

Чтобы использовать код выше:

   var operation = ValidatePersonID(personID);
   if (operation.Valid == false)
     {
        //BadRequest takes a string and C# uses co-variance to make it work.
        return BadRequest(operation.Result);
     }
   //otherwise just cast the type, but requires the cast to always work.
   var pe = (Person)operation.Result;
   ...

Возвращаемое значение: True или False, определяет возвращаемый тип. Мы по-прежнему выбираем строгую типизацию, используя приведение и/или требуемый тип ввода в BadRequest. Мы также включили проверку типов времени компиляции, так как если один из двух возвращаемых типов когда-либо изменится на что-то, отличное от string, или person, он не скомпилируется.

Я считаю это формой внедрения зависимости, при которой мы достигаем различного поведения в зависимости от того, что мы вводим. Пуристы С# не любят эту идею, но люди Typescript делают это постоянно.