用于转换动态对象的语法替代方法

时间:2012-04-21 04:27:18

标签: c# intellisense dynamic-language-runtime

我有一个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()

有人可以推荐更优雅的语法吗?我很高兴用IDynamicMetaObjectProvider编写DynamicDictionary的自定义实现。

更新:

有些人问为什么动态以及我的具体问题是什么。我有一个让我做这样的事情的系统:

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>

这可以成功。但事实并非如此。

3 个答案:

答案 0 :(得分:4)

为了获得智能感知,您必须在某个时刻将某些内容转换为非dynamic的值。 如果你发现自己做了很多,你可以使用辅助方法来缓解疼痛:

GetFoo(dictionary.Foo).SomeMethod();

但这并不比你已经得到的改进大很多。获得智能感知的唯一方法是将值转换回非动态类型或首先避免使用dynamic

如果您想使用Intellisense,通常最好避免首先使用dynamic

typedDictionary["foo"].SomeMethod();

您的示例使您可能对dynamic对象的结构有特定的期望。考虑是否有办法创建一个满足您需求的静态类结构。

更新

响应您的更新:如果您不想彻底更改语法,我建议您使用索引器,以便您的语法如下所示:

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

这是我的理由:

  1. 与动态方法相比,您只需添加四个字符(并减去一个字符)。
  2. 让我们面对现实吧:你正在使用&#34;魔术弦。&#34;通过要求实际字符串,对于查看此代码的程序员来说,这一事实将立即显而易见。使用dynamic方法,没有任何迹象表明&#34;索引&#34;从编译器的角度来看,它不是一个已知的值。
  3. 如果您愿意更改一些内容,可能需要调查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. 对方法签名的任何更改都会导致编译器发出立即标志,而不是代码运行时的运行时异常。
    3. 另一种可能稍微容易实现的语法(因为它不需要解析表达式树)可能是:

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

      这并没有为您提供上面列出的优势,但它仍然为您提供了类型安全性(因此也提供了智能感知)。选择你的毒药。

答案 1 :(得分:2)

除了 Explict Cast

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

安全投射

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

切换回静态调用(允许intellisense工作)的唯一方法是执行隐式投射

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

声明的转换是你唯一的选择,不能使用辅助方法,因为它们会被动态调用,给你同样的问题。

更新

不确定这是否更优雅,但不涉及施放一堆并在lambda之外获取智能感知:

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

答案 2 :(得分:0)

正如已经说过的那样(在问题和StriplingWarrior回答中),C#4 dynamic类型提供智能感知支持。提供此答案仅仅是为了解释为什么(基于我的理解)。

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实例。 +=运算符的使用实际上在两个不同的调用之间有所不同,因为涉及的类型是在运行时推断的(意味着编译器在编译时不理解或推断类型的使用)。 / p>

现在考虑一点变化:

dynamic v;

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

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

在此示例中,v可能是Int32 a String,具体取决于代码运行的时间。我知道,这是一个极端的例子,尽管它清楚地说明了这个问题。

考虑到单个dynamic变量在运行时可能代表任意数量的类型,编译器或IDE几乎不可能在它执行之前对它所代表的类型进行假设,因此设计 - 或dynamic变量潜在成员的编译时解析是不合理的(如果不是不可能的话)。