Syntax alternatives to casting of dynamic objects

2019-07-19 08:14发布

I have an implementation of DynamicDictionary where all of the entries in the dictionary are of a known type:

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

What I'd like is to be able to have Visual Studio Intellisense work when referencing a method of one of the dictionary entries:

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

The only way I've found to do this is:

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

Can anyone recommend a more elegant syntax? I'm comfortable writing a custom implementation of DynamicDictionary with IDynamicMetaObjectProvider.

UPDATE:

Some have asked why dynamics and what my specific problem is. I have a system that lets me do something like this:

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

In this case the method SomeMethodWithParameters has the signature

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

What I have right now for registering validation for individual parameters looks like this:

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

What I'd like it to be is:

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

This works using dynamics, but you lose intellisense after the reference to index - which is fine for now. The question is is there a clever syntactical way (other than the ones metioned above) to get Visual Studio to recognize the type somehow. Sounds so far like the answer is "no".

It seems to me that if there was a generic version of IDynamicMetaObjectProvider,

IDynamicMetaObjectProvider<T>

this could be made to work. But there isn't, hence the question.

3条回答
Bombasti
2楼-- · 2019-07-19 08:46

In order to get intellisense, you're going to have to cast something to a value that is not dynamic at some point. If you find yourself doing this a lot, you can use helper methods to ease the pain somewhat:

GetFoo(dictionary.Foo).SomeMethod();

But that isn't much of an improvement over what you've got already. The only other way to get intellisense would be to cast the value back to a non-dynamic type or avoid dynamic in the first place.

If you want to use Intellisense, it's usually best to avoid using dynamic in the first place.

typedDictionary["foo"].SomeMethod();

Your example makes it seem likely that you have specific expectations about the structure of your dynamic object. Consider whether there's a way to create a static class structure that would fulfill your needs.

Update

In response to your update: If you don't want to drastically change your syntax, I'd suggest using an indexer so that your syntax can look like this:

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

Here's my reasoning:

  1. You only add four characters (and subtract one) compared to the dynamic approach.
  2. Let's face it: you are using a "magic string." By requiring an actual string, this fact will be immediately obvious to programmers who look at this code. Using the dynamic approach, there's nothing to indicate that "index" is not a known value from the compiler's perspective.

If you're willing to change things around quite a bit, you may want to investigate the way Moq plays with expressions in their syntax, particularly the It.IsAny<T>() method. It seems like you might be able to do something more along these lines:

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

Unlike your current solution:

  1. This wouldn't break if you ended up changing the names of the parameters in the method signature: Just like the compiler, the framework would pay more attention to the location and types of the parameters than to their names.
  2. Any changes to the method signature would cause an immediate flag from the compiler, rather than a runtime exception when the code runs.

Another syntax that's probably slightly easier to accomplish (since it wouldn't require parsing expression trees) might be:

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

This doesn't give you the advantages listed above, but it still gives you type safety (and therefore intellisense). Pick your poison.

查看更多
Melony?
3楼-- · 2019-07-19 08:46

As has already been said (in the question and StriplingWarrior answer) the C# 4 dynamic type does not provide intellisense support. This answer is provided merely to provide an explanation why (based on my understanding).

dynamic to the C# compiler is nothing more than object which has only limited knowledge at compile-time which members it supports. The difference is, at run-time, dynamic attempts to resolve members called against its instances against the type for which the instance it represents knows (providing a form of late binding).

Consider the following:

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

In this snippet, v represents both an instance of Int32 (as seen in the first section of code) and an instance of String in the latter. The use of the += operator actually differs between the two different calls to it because the types involved are inferred at run-time (meaning the compiler doesn't understand or infer usage of the types at compile-time).

Now consider a slight variation:

dynamic v;

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

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

In this example, v could potentially be either an Int32 or a String depending on the time at which the code is run. An extreme example, I know, though it clearly illustrates the problem.

Considering a single dynamic variable could potentially represent any number of types at run-time, it would be nearly impossible for the compiler or IDE to make assumptions about the types it represents prior to it's execution, so Design- or Compile-time resolution of a dynamic variable's potential members is unreasonable (if not impossible).

查看更多
啃猪蹄的小仙女
4楼-- · 2019-07-19 08:56

Aside from Explict Cast,

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

or Safe Cast,

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

the only other way to switch back to static invocation (which will allow intellisense to work) is to do Implicit Cast:

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

Declared casting is your only option, can't use helper methods because they will be dynamically invoked giving you the same problem.

Update:

Not sure if this is more elegant but doesn't involve casting a bunch and gets intellisense outside of the 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(); 
查看更多
登录 后发表回答