Passing anonymous type as method parameters

2019-03-23 10:21发布

问题:

In my plugin architecture I am currently passing a plugin name (string), method name (string) and parameters (object array) to my plugin service to execute the specified method and return the result (of type T).

The plugin service's execute method can be seen below:

public TResult Execute<TResult>(string pluginName, string operation, params object[] input) {
    MethodInfo method = null;
    TResult result = default(TResult);

    var plugin = _plugins.Enabled().FirstOrDefault(x => x.GetType().Name.Equals(pluginName,  StringComparison.InvariantCultureIgnoreCase));

    if (plugin != null) {
        method = plugin.GetType().GetMethods().FirstOrDefault(x => x.Name == operation);
        if (method != null) {
            result = (TResult)method.Invoke(plugin, input);
        }
    }
    return result;
  }

An example usage:

var url = AppHelper.PluginService.Execute<string>(
    "ImagePlugin",
    "GetImageUrl",
    new object[] { image, size });

What I would rather do is pass in an anonymous type instead (as I think this is more readable) i.e.

var url = AppHelper.PluginService.Execute<string>(
    "ImagePlugin",
    "GetImageUrl",
    new { image = image, targetSize = size });

How would I change my Execute method to map the anonymous type properties to my plugin method parameters?

I had considered using the new dynamic type in .net 4.0 but I prefer to define my parameters on the plugin method rather than accepting one dynamic object.

Thanks Ben

[Update]

After looking through the ASP.NET MVC source code it seems simple enough to pull the anonymous type into an object dictionary e.g. RouteValueDictionary. With the help of reflection a linq expression is created dynamically. Although its a good implementation, I didn't really want all this extra complexity.

As per the comment below, I can achieve readability just by specifying my parameters inline (no need for the object array declaration):

var url = AppHelper.PluginService.Execute<string>("ImagePlugin", "GetImageUrl", image, size);

回答1:

There are some ways to make this possible although I wouldn't advice any of them.

First, you can use reflection which means you have to write a lot of additional (error-prone) code in your PluginService.Execute method to get the values you want.

Second, if you know the parameters of the anonymous type you are passing to your method you can use the technique described here. You can cast to another anonymous type inside your method that has the same properties. Here is another description of the same technique from Jon Skeet.

Third, you can use classes from the System.ComponentModel. For example, ASP.NET MVC uses this. It uses reflection under the hood. However, in ASP.NET MVC either the property names are well-known (controller and action for example) or their names don't matter because they are passed as-is to a controller method (id for example).



回答2:

I did eventually come across this post that demonstrates using anonymous types as dictionaries. Using this method you could pass the anonymous type as a method parameter (object) and access it's properties.

However, I would also add that after looking into the new dynamic features in .net 4.0 such as the ExpandoObject, it feels much cleaner to pass a dynamic object as a parameter:

        dynamic myobj = new ExpandoObject();
        myobj.FirstName = "John";
        myobj.LastName = "Smith";

        SayHello(myobj);
        ...........

        public static void SayHello(dynamic properties)
        {
           Console.WriteLine(properties.FirstName + " " + properties.LastName);
        }


回答3:

Use dynamic object for parameters if you want to pass an anonymous type. The execute method of a plugin should expect certain properties of a parameter object in order to work. By using dynamic keyword C# compiler will be instructed to not perform type check on a parameter and will allow to use strongly-typed syntax in the plugin code. The properties name resolution will happen in run-time and if a passed object did not have such properties an exception will be thrown.

var o = new { FirstName = "John", LastName = "Doe" };

var result = MyMethod(o);

string MyMethod(dynamic o)
{
    return o.FirstName + " " + o.LastName;
}

Read more in this blog post



回答4:

This example converts anonymous object to a dictionary:

IDictionary<string, object> AnonymousObjectToDictionary(object propertyBag)
{
    var result = new Dictionary<string, object>();
    if (propertyBag != null)
    {
        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(propertyBag))
        {
            result.Add(property.Name, property.GetValue(propertyBag));
        }
    }
    return result;
}

You can call it like this:

AnonymousObjectToDictionary(new { foo = 11, bar = "Be happy" });


回答5:

If it's an anonomous type from Linq, then you can easily do this by passing IEnumerable.

Here's an example of a receiving method

    public static void MyMethod<IEnumType>(ref IEnumerable<IEnumType> ienum)
    {
        using (DataTable dt = new DataTable())
        {
            ienum.First(ie => true).GetType().GetProperties().ToList().ForEach(pr => dt.Columns.Add(pr.Name, typeof(string))); //Parallelization not possible since DataTable columns must be in certain order

            ienum.ToList().ForEach(ie => //Parallelization not possible since DataTable rows not synchronized.
                {
                    List<object> objAdd = new List<object>();
                    ie.GetType().GetProperties().ToList().ForEach(pr => objAdd.Add(ie.GetType().InvokeMember(pr.Name, BindingFlags.GetProperty, null, ie, null))); //Parallelization not possible since DataTable columns must be in certain order
                    dt.Rows.Add(objAdd.ToArray());
                    objAdd.Clear();
                    objAdd = null;
                });
            //Do something fun with dt
        }
    }

Of course, since you're using reflection then you may see performance issues on slower machiens or where you have either a large IEnumerable or a lot of properties in T.



回答6:

Hi I have written a post about it:

http://blog.jorgef.net/2011/06/converting-any-object-to-dynamic.html

I hope that helps.



回答7:

I did this once. What you can do is get the parameters expected from the function through reflection. Then, you can build up your array of parameters by matching the names in the array of parameters with the keys of the anonymous object.

Hope that helps :-).



回答8:

public static void ExAnonymousType()
{
    var nguoi = new { Ten = "Vinh", Tuoi = 20 };
    Console.WriteLine(nguoi.Ten + " " + nguoi.Tuoi);
    DoSomeThing(nguoi);

}

private static void DoSomeThing(object nguoi)
{
    Console.WriteLine(nguoi.GetType().GetProperty("Ten").GetValue(nguoi,null));
}


回答9:

First of all, check System.Addin namespace, you might get some help there.

Second, you can create an interface of your own with specific method name and parameters, and let the plugin implement the interface. You can define plugin interface in a different project that can be referenced in both application as well as plugin project.