Подставной уткой typing с динамическим


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

Я думал, что написать хелпер, что за счет использования dynamic позволяет мне делать это в любом случае. Это реализуется с помощью универсального DuckObject<T> переопределив соответствующие API-интерфейсы базового типа DynamicObject и бросать исключения, когда член не статический или индексатора.

public class DuckObject<T> : DynamicObject
{
    private static readonly DuckObject<T> Duck = new DuckObject<T>();

    public static TValue Quack<TValue>(Func<dynamic, dynamic> quack)
    {
        return (TValue)quack(Duck);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var member = typeof(T).GetMember(binder.Name).SingleOrDefault();
        switch (member?.MemberType)
        {
            case MemberTypes.Field:
                result = typeof(T).InvokeMember(binder.Name, BindingFlags.GetField, null, null, null);
                break;
            case MemberTypes.Property:
                result = typeof(T).InvokeMember(binder.Name, BindingFlags.GetProperty, null, null, null);
                break;
            default:
                throw new StaticMemberNotFoundException<T>(binder.Name);
        }
        return true;
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        throw new InvalidOperationException($"Cannot use an indexer on '{typeof(T)}' because static types do not have them.");
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var member = typeof(T).GetMember(binder.Name).SingleOrDefault();
        switch (member?.MemberType)
        {
            case MemberTypes.Method:
                result = typeof(T).InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, null, args);
                break;
            default:
                throw new StaticMemberNotFoundException<T>(binder.Name);
        }
        return true;
    }
}

В тех случаях, когда меня нет TЯ могу использовать другой помощник, который работает с Type:

public class DuckObject
{
    private static readonly ConcurrentDictionary<Type, dynamic> Cache = new ConcurrentDictionary<Type, dynamic>();

    public static TValue Quack<TValue>(Type type, Func<dynamic, dynamic> quack)
    {
        var duck = Cache.GetOrAdd(type, t => Activator.CreateInstance(typeof(DuckObject<>).MakeGenericType(type)));
        return (TValue)quack(duck);
    }
}

Доступ к нестатическим членам вызывает StaticMemberNotFoundException:

public class StaticMemberNotFoundException<T> : Exception
{
    public StaticMemberNotFoundException(string missingMemberName)
    : base($"Type '{typeof(T)}' does not contain static member '{missingMemberName}'") { }
}

Пример-1 (Тест)

Предполагая, что существует два класса разделяя те же свойства и способ:

public class Foo
{
    public static string Baz => "Baaz-1";

    public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

public class Bar
{
    public static string Baz => "Baaz-2";

    public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

Я могу использовать их членов, как то:

DuckObject.Quack<string>(typeof(Foo), duck => duck.Baz).Dump();
DuckObject.Quack<string>(typeof(Bar), duck => duck.Baz).Dump();
DuckObject<Foo>.Quack<string>(duck => duck.Baz).Dump();
DuckObject<Bar>.Quack<string>(duck => duck.Baz).Dump();
DuckObject<Bar>.Quack<string>(duck => duck.Print("test"));
DuckObject<Bar>.Quack<string>(duck => duck.Print(null));
DuckObject<Bar>.Quack<string>(duck => duck["Bum!"]); // throws

Пример-2 (Реал)

Я использую его, чтобы открыть ValueMap из CronFieldС моей помощью cron-выражение парсер. Они инициализации значений, допустимых для конкретного поля:

public class CronSecond : CronField
{
  public static readonly IReadOnlyDictionary<string, int> ValueMap;

  static CronSecond()
  {
      ValueMap =
          Enumerable
              .Range(0, 60)
              .ToDictionary(x => x.ToString(), x => x);
  }

  [...]
}

Парсер использует их через T

internal static bool TryParse<T>(string input, out CronField cronField) where T : CronField
{
  [...]

  var fieldValues = DuckObject<T>.Quack<IReadOnlyDictionary<string, int>>(duck => duck.ValueMap);

  [...]
}

Вы думаете это хорошая идея или вы видите ничего, что могло бы быть улучшено?



Комментарии
1 ответ

Безопасность тип


Вы думаете это хорошая идея или вы видите ничего, что могло бы быть улучшено?

Язык, очевидно, является субъективным. Многие языки можно добиться той же цели, хотя и другим путем (как правило). Каждый язык имеет свои минусы и плюсы. Тип безопасности является большим плюсом для C#.
Безопасности тип приходит с свои недостатки. Типа безопасный компилятор значительно более педантична, так как он должен установить видах во все времена.

"Это хорошая идея?" - это немного широкая. Это требует от нас, чтобы перевесить преимущество иметь тип безопасности в сравнении с выгодами, не имея безопасного типа; который по своей сути требует от нас, чтобы оценить, насколько важен тип безопасности, и как необходимы ваши текущие подставной уткой набрав раствор.

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

Это источник бесконечных обсуждений. Опытный разработчик JavaScript будет использоваться для имеющих строгую типизацию, и, возможно, не думаю, что строгая типизация-это все, что необходимо. Разработчик C# может вместо того, чтобы твердо опираться на тип безопасности и будете чувствовать себя на неизведанной территории, когда они откажутся от тип безопасности, и поэтому отказываются работать без него.

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


Вы используете C#, так что я предполагаю, что вы хотите воспользоваться преимуществами языка C#. Безопасность типов является желательным. При наличии одной и той же задачи два решения, и только один из них является типобезопасным; вы должны выбрать безопасный вариант типа.
Эффективного решения без тип безопасности действительны только в отсутствие тип безопасных решений.


Опираясь на вывод компилятора.

В типа безопасный языка, отношения должны быть четко определены. Что является целью безопасности тип: он только позволяет вещам он знает, являются правильными. Без отметить, что Foo.Print() и Bar.Print() есть что-то общее; то компилятор не думаю , что они имеют что-то общее только потому, что они имеют одинаковую подпись.

Например:

public class NumberMultiplier
{
public int Execute(int a, int b) { return a * b; }
}

public class UserStateUpdater
{
public void Execute(int userId, int stateId) { /* set User state in DB */ }
}

Эти классы должны быть отведена таким образом, что вы можете reusably звонок Execute на каждом классе, поскольку они имеют одинаковую подпись? Я не вижу причин.

Основываясь на вашем предположении, что компилятор видит сходство между Foo.Print() и Bar.Print()это означает, что компилятор будет провести подобную параллель между NumberMultiplier.Execute() и UserStateUpdater.Execute().

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

Учитывая все это, представьте себе, если любой из этих классов является частью библиотеки, которая используется. Потребитель сказал, что библиотека будет эффективно быть снята с помощью метода с аналогичной подписью, regardlesss в какое пространство имен или класс, он работает над этим.
Представьте, если оба методы являются частью двух отдельных библиотек, которые используются. Что тогда?


Отношения должны быть явно определены

Ваши ожидания (и обходной путь, чтобы включить это предположение) чувствует себя очень грязным. Игнорирование static на секунду; если у вас есть два класса, которые имеют такое же свойство/метод и вы хотите указать на связь между Foo.Print() и Bar.Print()вы должны заявить, что отношения, как правило, по наследству или интерфейс.

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

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

Проблема здесь заключается в том, что это столкновение намерений:


  • Статика существует для обеспечения глобальной информации, которая должна быть универсально правильным.

  • Имеющих значение, которое является различным для разных классов по сути означает, что значение не является универсально правильным, так как он разный на разных уровнях.

Второй пункт фактически сводит на нет необходимость для статического.

Я думаю, что вы путаете статический собственность и многоразовые/обзоры типа.


В случае использования

public class Foo
{
public static string Baz => "Baaz-1";

public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

public class Bar
{
public static string Baz => "Baaz-2";

public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

Я действительно не могу придумать случай, где это является уместным и лучшим подходом. Второй (реальный) пример не помогает мне прояснить это. Я понимаю код, но не почему статика-это лучший подход здесь.

Если вы ожидаете, чтобы resuably обращаться к статическим свойствам типов; это предполагает, что вы уже хотите, чтобы полагаться на систему, в которой используются динамически (точно так же значения), которое просто не, что c# построен на (Делфи, с другой стороны, принял такой подход к сердцу). Хотя это не значит, что это невозможно реализовать это в C#, вы ортогонально идти против зерна на C#. Это создает осложнение основного кода и очень много работы для разработчиков, которым нужно оправдать, демонстрируя, что вы выиграют от этого. Я не вижу оснований для этого в вашем разместил код или объяснение.

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


Более четкой альтернативы

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

public class MyStaticThing
{
public string Baz;
public void Print(string message) => Console.WriteLine(message ?? Baz);
}

public class Foo
{
public static MyStaticThing Static = new MyStaticThing()
{
Baz = "Baaz-1"
};
}

public class Bar
{
public static MyStaticThing Static = new MyStaticThing()
{
Baz = "Baaz-2"
};
}


  • Foo еще имеет статическое свойство. Каждый Foo объекты же MyStaticThing.

  • Foo.Static имеет разные значения Bar.Staticно они оба имеют те же свойства/методы (вводится в действие по видам безопасности).

  • Реализации MyStaticThing класс удержало тебя от того, нарушает сухой (ваш Print() методы были четко скопировать и вставить).

  • Вы можете обрабатывать как статическое свойство значения в многоразовые образом:

public void HandleMyStaticThing(MyStaticThing myThing)
{
//....
}


Ваш ответ на самом деле решить проблему?

Я смотрел на это снова и снова, и я просто не могу увидеть цель вашего утиной типизацией. Кажется, усложнять синтаксис, не предоставляя ничего взамен.

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


DuckObject.Кря(для вызова typeof(Фу), дак => утки.Баз).Дамп();
DuckObject.Кря(для вызова typeof(бар), дак => утки.Баз).Дамп();
DuckObject<Фу>.Кря(утка => утки.Баз).Дамп();
DuckObject<Бар>.Кря(утка => утки.Баз).Дамп();
DuckObject<Бар>.Кря(утка => утки.Печати("испытание"));
DuckObject<Бар>.Кря(утка => утки.Печати(значение null));
DuckObject<Бар>.Кря(утка => Утка["бум!"]); // броски

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


  • DuckObject.Quack<string>(typeof(Foo), duck => duck.Baz) (и подобные) можно переписать как Foo.Baz (и подобные).

  • DuckObject<Foo>.Quack<string>(duck => duck.Baz) (и подобные) можно переписать как Foo.Baz (и подобные).

Есть одно интересное различие:

DuckObject<Bar>.Quack<string>(duck => duck["Bum!"]);

Это бросает во время выполнения исключение.

Bar["Bum!"];

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

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


Дополнительно:

DuckObject<Bar>.Quack<string>(duck => duck.Baz);
DuckObject<Bar>.Quack<string>(duck => duck["Bum!"]);

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

Это просто не тот случай!

Bar myBar = new Bar() { Name = "Test" };

var nonStaticValue = myBar.Name;
var staticValue = Bar.Baz;

Обратите внимание на разницу между myBar.Name и Bar.Baz. Один ссылается на объект, другой ссылается на тип. В этом принципиальная разница.

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

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

4
ответ дан 16 мая 2018 в 11:05 Источник Поделиться