Синтаксические альтернативы приведению динамических объектов

У меня есть реализация DynamicDictionary, в которой все записи в словаре имеют известный тип:

public class FooClass
{
    public void SomeMethod()
    {
    }
}

dynamic dictionary = new DynamicDictionary<FooClass>();

dictionary.foo = new FooClass();
dictionary.foo2 = new FooClass();
dictionary.foo3 = DateTime.Now;  <--throws exception since DateTime is not FooClass

Я бы хотел, чтобы Visual Studio Intellisense работала при ссылке на метод одной из записей словаря:

dictionary.foo.SomeMethod()  <--would like SomeMethod to pop up in intellisense

Единственный способ, который я нашел для этого, это:

((FooClass)dictionary.foo).SomeMethod()

Может ли кто-нибудь порекомендовать более элегантный синтаксис? Мне удобно писать собственную реализацию DynamicDictionary с помощью IDynamicMetaObjectProvider.

ОБНОВИТЬ:

Некоторые спрашивали, почему динамика и в чем конкретно моя проблема. У меня есть система, которая позволяет мне сделать что-то вроде этого:

UI.Map<Foo>().Action<int, object>(x => x.SomeMethodWithParameters).Validate((parameters) =>
{
    //do some method validation on the parameters
    return true;  //return true for now
}).WithMessage("The parameters are not valid");

В этом случае метод SomeMethodWithParameters имеет сигнатуру

public void SomeMethodWithParameters(int index, object target)
{
}

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

UI.Map<Foo>().Action<int, object>(x => x.SomeMethodWithParameters).GetParameter("index").Validate((val) =>
{
     return true;  //valid
}).WithMessage("index is not valid");

Я бы хотел, чтобы это было:

UI.Map<Foo>().Action<int, object(x => x.SomeMethodWithParameters).index.Validate((val) =>
{
    return true;
}).WithMessage("index is not valid");

Это работает с использованием динамики, но вы теряете интеллигентность после ссылки на индекс, что пока нормально. Вопрос в том, есть ли умный синтаксический способ (кроме упомянутых выше), чтобы заставить Visual Studio каким-то образом распознать тип. Пока звучит так, будто ответ «нет».

Мне кажется, что если бы существовала универсальная версия IDynamicMetaObjectProvider,

IDynamicMetaObjectProvider<T>

это можно заставить работать. Но его нет, отсюда и вопрос.


person Joe Enzminger    schedule 21.04.2012    source источник
comment
Не могли бы вы объяснить, зачем вам нужен динамический словарь? Или динамика вообще, если уж на то пошло.   -  person SimpleVar    schedule 21.04.2012
comment
Во всяком случае, нет... Вы не можете добавить интеллект к динамическим типам. Однако то, что вы МОЖЕТЕ сделать, это не иметь динамических типов!   -  person SimpleVar    schedule 21.04.2012
comment
Смотрите обновление. Предложения приветствуются.   -  person Joe Enzminger    schedule 23.04.2012


Ответы (3)


Чтобы получить intellisense, вам нужно в какой-то момент привести что-то к значению, которое не равно dynamic. Если вы обнаружите, что делаете это часто, вы можете использовать вспомогательные методы, чтобы немного облегчить боль:

GetFoo(dictionary.Foo).SomeMethod();

Но это не так много улучшений по сравнению с тем, что у вас уже есть. Единственный другой способ получить intellisense — вернуть значение обратно к нединамическому типу или вообще избежать dynamic.

Если вы хотите использовать Intellisense, обычно лучше вообще избегать использования dynamic.

typedDictionary["foo"].SomeMethod();

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

Обновлять

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

UI.Map<Foo>().Action<int, object>(x => x.SomeMethodWithParameters)["index"].Validate((val) => {...});

Вот мои рассуждения:

  1. Вы добавляете только четыре символа (и вычитаете один) по сравнению с динамическим подходом.
  2. Посмотрим правде в глаза: вы используете волшебную строку. Если требуется фактическая строка, этот факт сразу станет очевиден для программистов, которые посмотрят на этот код. Используя подход dynamic, ничто не указывает на то, что индекс не является известным значением с точки зрения компилятора.

Если вы хотите немного что-то изменить, вы можете изучить то, как Moq играет с выражениями в их синтаксисе, особенно с методом It.IsAny<T>(). Кажется, вы могли бы сделать что-то еще в этом направлении:

UI.Map<Foo>().Action(
    (x, v) => x.SomeMethodWithParameters(
        v.Validate<int>(index => {return index > 1;})
            .WithMessage("index is not valid"),
        v.AlwaysValid<object>()));

В отличие от вашего текущего решения:

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

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

UI.Map<Foo>().Action((x, v) => x.SomeMethodWithParameters)
    .Validate(v => new{
        index = v.ByMethod<int>(i => {return i > 1;}),
        target = v.IsNotNull()});

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

person StriplingWarrior    schedule 21.04.2012
comment
Спасибо. Смотрите обновление. Звучит как проблема без хорошего решения (на данный момент), но предложения приветствуются. - person Joe Enzminger; 23.04.2012
comment
@JoeEnzminger: я думаю, что в этом случае есть решения получше, чем dynamic. Смотрите мое обновление. - person StriplingWarrior; 23.04.2012
comment
Мне очень нравится ваше первое предложение. Я проголосовал за это. Если я смогу заставить его работать, я приму его. - person Joe Enzminger; 23.04.2012
comment
@StriplingWarrior, вы не можете использовать вспомогательные методы для восстановления intellisense, они также вызываются динамически. - person jbtule; 24.04.2012
comment
Спасибо за все предложения. В итоге я вернулся к индексатору строк, хотя ваш ответ @jbtule тоже был хорош. В итоге я получил синтаксис, работающий и для проверки параметров. Не совсем относится к вопросу, но отличный совет. - person Joe Enzminger; 25.04.2012

Помимо явного приведения,

((FooClass)dictionary.foo).SomeMethod();

или Safe Cast,

(dictionary.foo as FooClass).SomeMethod();

единственный другой способ вернуться к статическому вызову (который позволит работать intellisense) — это выполнить Implicit Cast:

FooClass foo = dictionary.foo;
foo.SomeMethod().

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

Обновление:

Не уверен, что это более элегантно, но не требует приведения кучи и получает интеллект за пределами лямбды:

public class DynamicDictionary<T>:IDynamicMetaObjectProvider{

    ...

    public T Get(Func<dynamic,dynamic> arg){
            return arg(this);
    }

    public void Set(Action<dynamic> arg){
            arg(this);
    }
}
...
var dictionary = new DynamicDictionary<FooClass>();

dictionary.Set(d=>d.Foo = new FooClass());
dictionary.Get(d=>d.Foo).SomeMethod(); 
person jbtule    schedule 24.04.2012
comment
Обновление интересное, но я не уверен, что оно сильно отличается от Dictionary.Get(Foo). Спасибо за предложение! - person Joe Enzminger; 25.04.2012

Как уже было сказано (в вопросе и ответе StriplingWarrior), тип C# 4 dynamic не обеспечить поддержку IntelliSense. Этот ответ предоставляется просто для объяснения почему (насколько я понимаю).

dynamic для компилятора C# - это не что иное, как object, который во время компиляции имеет лишь ограниченное представление о том, какие члены он поддерживает. Разница в том, что во время выполнения dynamic пытается разрешить члены, вызываемые для своих экземпляров, по типу, который известен экземпляру, который он представляет (обеспечивая форму позднего связывания).

Рассмотрим следующее:

dynamic v = 0;
v += 1;
Console.WriteLine("First: {0}", v);
// ---
v = "Hello";
v += " World";
Console.WriteLine("Second: {0}", v);

В этом фрагменте v представляет собой экземпляр Int32 (как показано в первом разделе кода) и экземпляр String в последнем. Использование оператора += на самом деле различается между двумя разными его вызовами, потому что задействованные типы выводятся во время выполнения (это означает, что компилятор не понимает или не делает вывод об использовании типов во время компиляции).

Теперь рассмотрим небольшую вариацию:

dynamic v;

if (DateTime.Now.Second % 2 == 0)
    v = 0;
else
    v = "Hello";

v += 1;
Console.WriteLine("{0}", v);

В этом примере v потенциально может быть Int32 или String в зависимости от времени выполнения кода. Крайний пример, я знаю, хотя он ясно иллюстрирует проблему.

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

person M.Babcock    schedule 21.04.2012
comment
Спасибо за объяснение динамики. Я не согласен с вашим последним абзацем. Я думаю, что это можно было бы сделать, и это было бы не так сложно, но это функция на уровне языка, и вряд ли она будет реализована в ближайшее время. - person Joe Enzminger; 23.04.2012
comment
Какие участники вы ожидаете от v в моем втором примере после оператора if? - person M.Babcock; 23.04.2012