简体   繁体   English

强类型属性引用多个类,没有通用接口(C#)

[英]Strongly-typed property reference to multiple classes with no common interface (C#)

The System.Windows.Documents namespace includes a number of classes with an Inlines property of type InlineCollection . System.Windows.Documents命名空间包含许多具有InlineCollection类型的Inlines属性的类。 For example, the Paragraph , Bold and Hyperlink classes all have this property. 例如, ParagraphBoldHyperlink类都具有此属性。

Each of these classes is decorated with ContentPropertyAttribute ... 这些类中的每一个都使用ContentPropertyAttribute修饰......

[ContentPropertyAttribute("Inlines")]
public class Paragraph : Block

... which means that it is easy enough, using reflection, to detect that a given object exposes this property. ...这意味着使用反射很容易检测给定对象是否暴露了这个属性。

However, I need to be able to access this property in a strongly-typed manner across a selection of the types that implement it. 但是,我需要能够以强类型的方式在实现它的所选类型中访问此属性。

I am a little surprised that Microsoft didn't make all these classes implement an " IInlineContainer " interface, which would have made type checking and casting very easy. 我有点惊讶的是,微软没有让所有这些类实现一个“ IInlineContainer ”接口,这样就可以很容易地进行类型检查和转换。

However, in the absence of such an interface, is there any way to fake this polymorphic functionality, ideally without littering my code with lots of conditions and type checking? 但是,在没有这样的接口的情况下,有没有办法伪造这种多态功能,理想情况下不会乱丢我的代码有很多条件和类型检查?

Many thanks for your ideas, 非常感谢你的想法,

Tim 蒂姆

Edit: 编辑:

Thanks for your suggestions. 谢谢你的建议。 A number of people have suggested the idea of a wrapper class, but this is not possible in my situation, as the target objects are not created by my code, but by the other classes in the .NET framework, for example the Xaml parser or the RichTextBox control (in which the containing FlowDocument is being edited). 很多人都提出了包装类的想法,但在我的情况下这是不可能的,因为目标对象不是由我的代码创建的,而是由.NET框架中的其他类创建的,例如Xaml解析器或RichTextBox控件(正在编辑包含FlowDocument )。

Edit 2: 编辑2:

There have been several great suggestions here and I thank everyone who shared their ideas. 这里有几个很棒的建议,我感谢所有分享他们想法的人。 The solution I have chosen to implement employs extension methods, which was suggested by @qstarin, although I have refined the concept to suit my needs, as follows: 我选择实施的解决方案采用了@qstarin建议的扩展方法,尽管我已经根据我的需求改进了这个概念,如下所示:

public static InlineCollection GetInlines(
    this FrameworkContentElement element)
{
    if (element == null) throw new ArgumentNullException("element");

    if (element is Paragraph)
    {
        return ((Paragraph) element).Inlines;
    }
    else if (element is Span) // also catches Bold, Italic, Unerline, Hyperlink
    {
        return ((Span)element).Inlines;
    }
    else 
    {
        return null;
    }
}

Although this approach requires conditional logic and type casting (which I said I wanted to avoid) the use of extension methods means that it only needs to be implemented in one place, leaving my various calling methods uncluttered. 虽然这种方法需要条件逻辑和类型转换(我说我想避免),扩展方法的使用意味着它只需要在一个地方实现,让我的各种调用方法整洁。

Extension methods. 扩展方法。

public static class InlineContainerExtensions {
    public static InlineContainer GetInlines(this Paragraph inlineContainer) {
        return inlineContainer.Inlines;
    }

    public static InlineContainer GetInlines(this Bold inlineContainer) {
        return inlineContainer.Inlines;
    }
}

If you didn't need to access it in a strongly-typed manner, but just without reflection, you could use dynamic : 如果您不需要以强类型方式访问它,但只是没有反射,则可以使用dynamic

dynamic doc = new Bold()
doc.InlineCollection. ...
doc = new Paragraph()
doc.InlineCollection. ...

Another option is to define a wrapper, that exposes a property with the same name, and has an overloaded constructor that takes Bold , Paragraph , etc. 另一种选择是定义一个包装器,它公开一个具有相同名称的属性,并且有一个重载的构造函数,它接受BoldParagraph等。

You could implement a wrapper class that exposes an Inlines property and delegates via reflection to the contained object. 您可以实现一个包装类,该类公开Inlines属性并通过反射委托给包含的对象。

Decide if you want to validate that the wrapped object indeed has Inlines in your constructor or when trying to reference it 确定是否要验证包装对象在构造函数中是否确实具有Inlines ,或者在尝试引用它时

Employ the Adapter Pattern , write one class for each of those classes you wish to handle, effectively wrapping them in a layer implementing a common layer. 使用适配器模式 ,为您希望处理的每个类编写一个类,有效地将它们包装在实现公共层的层中。

To make the classes discoverable, I would use reflection, tag each such class with an attribute for which class they handle, ie.: 为了使类可被发现,我将使用反射,用它们处理的类的属性标记每个这样的类,即:

[InlineContainerAdapter(typeof(SpecificClass1))]
public class WrapSpecificClass1 : IInlineContainer

and use reflection to find them. 并使用反射来找到它们。

This would give you several benefits: 这会给你带来好处:

  1. You don't have to deal with dynamic, or similar solutions 您不必处理动态或类似的解决方案
  2. While you have to use reflection to find the classes, the code you're actually executing once you've created the adapter is 100% yours, hand-coded 虽然你必须使用反射来查找类,但是一旦你创建了适配器,你实际执行的代码是100%你的,手工编码
  3. You can create adapters for classes that doesn't really implement what you need in the same manner as the rest, by just writing the adapter different 您可以通过编写不同的适配器,为那些没有真正实现所需内容的类创建适配器,方法与其余类相同

If this sounds like an interesting solution, leave a comment and I'll put up a working complete example. 如果这听起来像一个有趣的解决方案,请留下评论,我会提出一个完整的例子。

One way of doing this (apart from using dynamic , which is the easiest solution IMO), you can create dynamically generated methods to return the inlines: 这样做的一种方法(除了使用dynamic ,这是最简单的IMO解决方案),您可以创建动态生成的方法来返回内联:

Func<object, InlineCollection> GetInlinesFunction(Type type)
{
    string propertyName = ...;
    // ^ check whether type has a ContentPropertyAttribute and
    // retrieve its Name here, or null if there isn't one.
    if (propertyName == null)
        return null;
    var p = Expression.Parameter(typeof(object), "it");
    // The following creates a delegate that takes an object
    // as input and returns an InlineCollection (as long as
    // the object was at least of runtime-type "type".
    return Expression.Lambda<Func<object, InlineCollection>>(
        Expression.Property(
            Expression.Convert(p, type),
            propertyName),
        p).Compile();
}

You'd have to cache these somewhere, though. 不过,你必须在某处缓存这些内容。 A static Dictionary<Type, Func<object, InlineCollection>> comes to mind. 我想到了静态Dictionary<Type, Func<object, InlineCollection>> Anyway, when you have, you can simply make an extension method: 无论如何,当你有,你可以简单地做一个扩展方法:

public static InlineCollection GetInlines(this TextElement element)
{
    Func<object, InlineCollection> f = GetCachedInlinesFunction(element.GetType());
    if (f != null)
        return f(element);
    else
        return null;
}

Now, with this in place, just use 现在,有了这个,只需使用

InlineCollection coll = someElement.GetInlines();

Because you can check in your GetCachedInlinesFunction whether the property really exists or not, and handle that in a neat fashion, you won't have to litter your code with try catch blocks like you have to when you're using dynamic . 因为您可以在GetCachedInlinesFunction检查属性是否真的存在,并以一种简洁的方式处理它,所以您不必像使用dynamic时那样使用try catch块来丢弃代码。

So, your dream-code would be: 所以,你的梦想代码将是:

foreach (var control in controls) {
  var ic = control as IInlineContainer;
  if (ic != null) {
    DoSomething(ic.Inlines);
  }
}

I don't see why you don't want to create a strongly typed wrapper class that uses reflection. 我不明白为什么你不想创建一个使用反射的强类型包装类。 With this class (no error handling): 使用此类(无错误处理):

public class InlinesResolver {
  private object _target;
  public InlinesResolver(object target) {
    _target = target;
  }
  public bool HasInlines {
    get {
      return ResolveAttribute() != null;
    }
  }
  public InlineCollection Inlines {
    get {
      var propertyName = ResolveAttribute().Name;
      return (InlineCollection)
        _target.GetType().GetProperty(propertyName).GetGetMethod().Invoke(_target, new object[] { });
    }
  }
  private ContentPropertyAttribute ResolveAttribute() {
    var attrs = _target.GetType().GetCustomAttributes(typeof(ContentPropertyAttribute), true);
    if (attrs.Length == 0) return null;
    return (ContentPropertyAttribute)attrs[0];
  }
}

You could almost get to your dream-code: 你几乎可以达到你的梦想代码:

foreach (var control in controls) {
  var ir = new InlinesResolver(control);
  if (ir.HasInlines) {
    DoSomething(ir.Inlines);
  }
}

You could always superclass them (eg InlineParagraph, InlineBold, etc) and have each of your superclasses implement an IInlineContainer interface like you suggested. 你总是可以对它们进行超类(例如InlineParagraph,InlineBold等)并让你的每个超类都像你建议的那样实现一个IInlineContainer接口。 Not the quickest or cleanest solution, but you at least have them all descending from the same interface. 不是最快或最干净的解决方案,但你至少让它们都来自同一个界面。

Depending on your use-case, you could create a public Api that delegated its work to a private method that takes a dynamic . 根据您的用例,您可以创建一个公共Api,将其工作委派给一个采用dynamic的私有方法。 This keeps the strong typing for your public Api and eliminates code duplication, even though it falls back to using dynamic internally. 这样可以保持公共Api的强类型,并消除代码重复,即使它回退到内部使用dynamic

public void DoSomethingwithInlines(Paragraph p) {
    do(p);
}

public void DoSomethingwithInlines(Bolb b) {
    do(b);
}

private void do(dynamic d) {
    // access Inlines here, using c# dynamic
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在.Net / C#中,是否为强类型? - In .Net/C#, is null strongly-typed? C#中介器中的强类型消息处理程序 - Strongly-typed Message Handlers in Mediator in C# 易于使用的强类型JSON库用于C# - Easy to use strongly-typed JSON library for C# 在C#中的通用FillDataSet方法中映射强类型数据集? - Mapping strongly-typed DataSets in a generic FillDataSet method in C#? 是否可以将强类型的类作为C#中方法的参数传递? - Is it possible to pass a strongly-typed class as a parameter of a method in C#? Objective-C转换为C#:如果字典是强类型的,如何在C#中使用多个类型的.plist? - Objective-C converting to C#: How can I use a .plist with multiple types in C# if Dictionary is strongly-typed? 使用yield return的强类型方法接口 - Strongly-typed method interface using yield return 有没有办法定义现有原始类型(如 `string` 或 `int`)的 C# 强类型别名? - Is there a way to define C# strongly-typed aliases of existing primitive types like `string` or `int`? LINQ加入,然后分组为强类型的类列表 - LINQ join, then group by, into strongly-typed List of classes 如何使用c#中的强类型数据库从DataTable AutoIncrementColumn获取自动编号? - How to get an AutoNumber from DataTable AutoIncrementColumn with a Strongly-Typed Database in c#?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM