传递匿名类型作为方法参数

23
在我的插件架构中,我目前正在将插件名称(字符串)、方法名称(字符串)和参数(对象数组)传递给我的插件服务来执行指定的方法并返回结果(类型为T)。
插件服务的执行方法如下所示:
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;
  }

一个使用示例:
var url = AppHelper.PluginService.Execute<string>(
    "ImagePlugin",
    "GetImageUrl",
    new object[] { image, size });

我更愿意传递一个匿名类型(因为我认为这更易读),例如:

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

我该如何修改我的Execute方法,将匿名类型属性映射到我的插件方法参数上?
我考虑使用.net 4.0中的新动态类型,但我更喜欢在插件方法中定义我的参数,而不是接受一个动态对象。
谢谢 Ben
[更新]
在查看了ASP.NET MVC源代码后,似乎很容易将匿名类型提取到对象字典(例如RouteValueDictionary)中。借助反射和linq表达式,可以动态创建实现。虽然这是一种好的实现方式,但我并不想增加额外的复杂性。
根据下面的评论,我可以通过内联指定参数来实现可读性(无需声明对象数组)。
var url = AppHelper.PluginService.Execute<string>("ImagePlugin", "GetImageUrl", image, size);

2
由于您正在使用params关键字,因此可以使用image, size代替new object[] { image, size }。这将使代码更易读,并且由于Invoke方法接受对象数组,因此我会保留方法签名不变。 - Necros
10个回答

22

我最终找到了这篇文章,展示了如何使用匿名类型作为字典。使用该方法,您可以将匿名类型作为方法参数(对象)传递并访问其属性。

然而,我还想补充一点,在研究了 .net 4.0 中的新动态特性(例如 ExpandoObject)之后,使用动态对象作为参数会更加清晰明了:

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

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

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

12

如果想传递匿名类型,请使用动态对象作为参数。插件的执行方法应该期望参数对象的某些属性才能正常工作。使用C#中的dynamic关键字,编译器将被指示不对参数进行类型检查,并允许在插件代码中使用强类型语法。属性名称解析将在运行时发生,如果传递的对象没有这些属性,则会抛出异常。

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

var result = MyMethod(o);

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

在这篇博客文章中阅读更多内容


9
有几种方法可以实现这一点,尽管我不建议使用任何一种。
首先,您可以使用反射,这意味着您必须在PluginService.Execute方法中编写大量额外的(容易出错的)代码来获取所需的值。
其次,如果您知道传递给方法的匿名类型的参数,可以使用此处描述的技术。您可以在方法内部转换为另一个具有相同属性的匿名类型。 这里是Jon Skeet对相同技术的另一种描述。
第三,您可以使用System.ComponentModel中的类。例如,ASP.NET MVC使用它。它在幕后使用反射。但是,在ASP.NET MVC中,属性名称要么是众所周知的(例如controlleraction),要么它们的名称无关紧要,因为它们按原样传递给控制器方法(例如id)。

Ronald,感谢提供的链接。我开始觉得我应该坚持使用当前的实现。然而,在ASP.NET MVC中,例如在HTML助手和路由中,不是广泛使用这种方法吗?我相信一定有一种“好”的方法来做到这一点,否则微软就不会这样做了 :) - Ben Foster
好的,我用另一种方式更新了我的答案,以实现你想要的功能。 - Ronald Wildenberg
我已将您的回复标记为答案,因为它是最完整的。然而,在查看MVC源代码以了解他们如何实现此功能后,我将坚持使用自己的实现方式,但会采用Necros的建议来提高可读性。 - Ben Foster
你的Jon Skeet链接已经失效。 - Detail
谢谢,已修复链接。 - Ronald Wildenberg

7

这个例子将匿名对象转换为字典:

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;
}

你可以这样调用:
AnonymousObjectToDictionary(new { foo = 11, bar = "Be happy" });

3
如果这是来自Linq的匿名类型,那么只需通过传递IEnumerable就可以轻松完成。
以下是接收方法的示例:
    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
        }
    }

当然,由于您正在使用反射,所以在较慢的机器上或者T具有大量属性或大型IEnumerable时可能会出现性能问题。

2

1
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));
}

1

1

我曾经做过这件事。您可以通过反射获取函数期望的参数。然后,您可以通过将参数数组中的名称与匿名对象的键进行匹配来构建参数数组。

希望这有所帮助 :-).


0
首先,检查System.Addin命名空间,你可能会在那里得到一些帮助。
其次,你可以创建一个自己的接口,具有特定的方法名称和参数,并让插件实现该接口。你可以在不同的项目中定义插件接口,这些项目可以被应用程序和插件项目引用。

我们已经在使用接口和StructureMap来调用特定的插件类型。这主要是为了用户可能会即时创建并想在UI中使用的插件(例如,他们可能想要将上面的ImagePlugin替换为从Amazon S3获取图像的插件)。 - Ben Foster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接