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

时间:2010-07-09 16:13:14

标签: c# interface polymorphism

System.Windows.Documents命名空间包含许多类Inlines InlineCollection属性的类。例如,ParagraphBoldHyperlink类都具有此属性。

这些课程中的每一个都用ContentPropertyAttribute ...

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

...这意味着使用反射很容易检测给定对象是否暴露了这个属性。

但是,我需要能够以强类型的方式在实现它的所选类型中访问此属性。

微软并没有让所有这些类都实现“IInlineContainer”接口,这让我很感到有点惊讶,这样就可以很容易地进行类型检查和转换。

然而,在没有这样的接口的情况下,有没有办法伪造这种多态功能,理想情况下不会乱丢我的代码,有很多条件和类型检查?

非常感谢你的想法,

蒂姆

修改

感谢您的建议。很多人都提出了包装类的想法,但在我的情况下这是不可能的,因为目标对象不是由我的代码创建的,而是由.NET框架中的其他类创建的,例如Xaml解析器或RichTextBox控件(正在编辑包含FlowDocument)。

编辑2:

这里有几个很棒的建议,我感谢所有分享他们想法的人。我选择实施的解决方案采用@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;
    }
}

虽然这种方法需要条件逻辑和类型转换(我说我想避免),扩展方法的使用意味着它只需要在一个地方实现,让我的各种调用方法整洁。

8 个答案:

答案 0 :(得分:2)

扩展方法。

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

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

答案 1 :(得分:1)

如果您不需要以强类型方式访问它,但只是没有反思,则可以使用dynamic

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

另一种选择是定义一个包装器,它公开一个具有相同名称的属性,并且具有一个重载的构造函数,该构造函数需要BoldParagraph等。

答案 2 :(得分:1)

您可以实现一个包装类,该类公开Inlines属性并通过反射委托给包含的对象。

决定是否要在构造函数中或在尝试引用它时验证包装对象确实有Inlines

答案 3 :(得分:1)

使用Adapter Pattern,为每个要处理的类编写一个类,将它们有效地包装在实现公共层的层中。

为了使类可被发现,我会使用反射,用它们处理的类的属性标记每个这样的类,即:

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

并使用反射来查找它们。

这会给你带来好处:

  1. 您无需处理动态或类似的解决方案
  2. 虽然您必须使用反射来查找类,但是在创建适配器后实际执行的代码是100%,手工编码
  3. 只需编写不同的适配器,就可以为那些没有真正实现所需内容的类创建适配器,只需编写不同的适配器
  4. 如果这听起来像一个有趣的解决方案,请发表评论,我会提出一个完整的示例。

答案 4 :(得分:1)

这样做的一种方法(除了使用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();
}
但是,你必须在某处缓存这些内容。想到静态Dictionary<Type, Func<object, InlineCollection>>。无论如何,当你有,你可以简单地做一个扩展方法:

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

现在,有了这个,只需使用

InlineCollection coll = someElement.GetInlines();

因为您可以检查GetCachedInlinesFunction该属性是否真的存在,并以一种简洁的方式处理它,所以您不必像{I}}那样使用try catch块来丢弃您的代码当你使用dynamic时。

答案 5 :(得分:1)

所以,你的梦想代码将是:

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

我不明白为什么你不想创建一个使用反射的强类型包装类。使用此类(无错误处理):

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

你几乎可以找到你的梦想代码:

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

答案 6 :(得分:0)

你总是可以对它们进行超类(例如InlineParagraph,InlineBold等)并让你的每个超类都像你建议的那样实现一个IInlineContainer接口。不是最快或最干净的解决方案,但你至少让它们都来自同一个界面。

答案 7 :(得分:0)

根据您的用例,您可以创建一个公共Api,将其工作委派给采用dynamic的私有方法。这可以保持公共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
}