[英]Strongly-typed property reference to multiple classes with no common interface (C#)
System.Windows.Documents
命名空间包含许多具有InlineCollection
类型的Inlines
属性的类。 例如, Paragraph
, Bold
和Hyperlink
类都具有此属性。
这些类中的每一个都使用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. ...
另一种选择是定义一个包装器,它公开一个具有相同名称的属性,并且有一个重载的构造函数,它接受Bold
, Paragraph
等。
您可以实现一个包装类,该类公开Inlines
属性并通过反射委托给包含的对象。
确定是否要验证包装对象在构造函数中是否确实具有Inlines
,或者在尝试引用它时
使用适配器模式 ,为您希望处理的每个类编写一个类,有效地将它们包装在实现公共层的层中。
为了使类可被发现,我将使用反射,用它们处理的类的属性标记每个这样的类,即:
[InlineContainerAdapter(typeof(SpecificClass1))]
public class WrapSpecificClass1 : IInlineContainer
并使用反射来找到它们。
这会给你带来好处:
如果这听起来像一个有趣的解决方案,请留下评论,我会提出一个完整的例子。
这样做的一种方法(除了使用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.