繁体   English   中英

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

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

System.Windows.Documents命名空间包含许多具有InlineCollection类型的Inlines属性的类。 例如, 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;
    }
}

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

扩展方法。

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

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

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

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

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

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

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

使用适配器模式 ,为您希望处理的每个类编写一个类,有效地将它们包装在实现公共层的层中。

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

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

并使用反射来找到它们。

这会给你带来好处:

  1. 您不必处理动态或类似的解决方案
  2. 虽然你必须使用反射来查找类,但是一旦你创建了适配器,你实际执行的代码是100%你的,手工编码
  3. 您可以通过编写不同的适配器,为那些没有真正实现所需内容的类创建适配器,方法与其余类相同

如果这听起来像一个有趣的解决方案,请留下评论,我会提出一个完整的例子。

这样做的一种方法(除了使用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检查属性是否真的存在,并以一种简洁的方式处理它,所以您不必像使用dynamic时那样使用try catch块来丢弃代码。

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

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

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

根据您的用例,您可以创建一个公共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
}

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM