具有“具有属性X”约束的generics函数?

我有一个第三方闭源应用程序,它导出一个COM接口,我通过Interop在我的C#.NET应用程序中使用它。 此COM接口导出许多对象,这些对象都显示为System.Object,直到我将它们转换为适当的接口类型。 我想分配所有这些对象的属性。 从而:

foreach (object x in BigComInterface.Chickens) { (x as Chicken).attribute = value; } foreach (object x in BigComInterface.Ducks) { (x as Duck).attribute = value; } 

但是分配属性很可能(由于特定于应用程序的原因,不可避免)抛出我想要恢复的exception,所以我真的想要一个try / catch。 从而:

 foreach (object x in BigComInterface.Chickens) { try { (x as Chicken).attribute = value; } catch (Exception ex) { // handle... } } foreach (object x in BigComInterface.Ducks) { try { (x as Duck).attribute = value; } catch (Exception ex) { // handle... } } 

显然,这样做会更加清晰:

 foreach (object x in BigComInterface.Chickens) { SetAttribute(x as Chicken, value); } foreach (object x in BigComInterface.Ducks) { SetAttribute(x as Duck, value); } void SetAttribute(T x, System.Object value) { try { x.attribute = value; } catch { // handle... } } 

看到问题了吗? 我的x值可以是任何类型,因此编译器无法解析.attribute 。 Chicken和Duck不在任何类型的inheritance树中,并且它们不共享具有.attribute的接口。 如果他们这样做,我可以在T上为该接口设置约束。 但由于课程是闭源的,这对我来说是不可能的。

在我的幻想中,我想要的是类似约束,要求参数具有.attribute属性 ,无论它是否实现给定的接口。 以机智,

 void SetAttribute(T x, System.Object value) where T:hasproperty(attribute) 

我不知道该怎么做,除了为鸡,鸭,牛,羊等每个切割/粘贴这个小的try / catch块。

我的问题是:当在编译时无法知道实现该属性的接口时,想要在对象上调用特定属性的问题有什么好的解决方法?

不幸的是,目前这很棘手。 在C#4中,动态类型可能对此有所帮助。 COM互操作是动态真正闪耀的地方之一。

但是,与此同时,唯一允许您拥有任何类型对象但没有接口限制的选项将是恢复使用reflection。

您可以使用reflection来查找“属性”属性,并在运行时设置它的值。

好吧,取决于您的exception处理代码是多么庞大(如果我没有弄错,可能就是这样),使用以下技巧可能会对您有所帮助:

 class Chicken { public string attribute { get; set; } } class Duck { public string attribute { get; set; } } interface IHasAttribute { string attribute { get; set; } } class ChickenWrapper : IHasAttribute { private Chicken chick = null; public string attribute { get { return chick.attribute; } set { chick.attribute = value; } } public ChickenWrapper(object chick) { this.chick = chick as Chicken; } } class DuckWrapper : IHasAttribute { private Duck duck = null; public string attribute { get { return duck.attribute; } set { duck.attribute = value; } } public DuckWrapper(object duck) { this.duck = duck as Duck; } } void SetAttribute(T x, string value) where T : IHasAttribute { try { x.attribute = value; } catch { // handle... } } 

不幸的是,唯一的方法是使用定义该属性的接口约束type参数,并在所有类型上实现。

由于您没有源,因此无法实现,因此您将不得不使用某种解决方法。 C#是静态类型的,因此不支持你想在这里使用的鸭子类型。 即将到来的最好的事情(在C#4中)将是将对象键入为dynamic并在执行时解析属性调用(请注意,此方法也不是通用的,因为您不能将generics类型参数约束为dynamic )。

为了不违反DRY原则,您可以使用reflection。 如果你真的想使用generics,你可以为每种类型的第三方对象使用包装类,并让包装器实现一个可用于约束generics类型参数的接口。

一个如何通过reflection完成它的例子。 但是代码没有经过测试。

  void SetAttribute(object chickenDuckOrWhatever, string attributeName, string value) { var typeOfObject = chickenDuckOrWhatever.GetType(); var property = typeOfObject.GetProperty(attributeName); if (property != null) { property.SetValue(chickenDuckOrWhatever, value); return; } //No property with this name was found, fall back to field var field = typeOfObject.GetField(attributeName); if (field == null) throw new Exception("No property or field was found on type '" + typeOfObject.FullName + "' by the name of '" + attributeName + "'."); field.SetValue(chickenDuckOrWhatever, value); } 

如果你要加速代码的性能,你可以缓存每种类型的chickenDuckOrWhatever的FieldInfo和PropertyInfo,并在反映之前先查阅字典。

提示: 要不必将 attributeName硬编码为字符串,可以使用C#6function名称 。 比如nameof(Chicken.AttributeName)。