System.Windows.Documents
命名空间包含许多类Inlines
InlineCollection
属性的类。例如,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;
}
}
虽然这种方法需要条件逻辑和类型转换(我说我想避免),扩展方法的使用意味着它只需要在一个地方实现,让我的各种调用方法整洁。
答案 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. ...
另一种选择是定义一个包装器,它公开一个具有相同名称的属性,并且具有一个重载的构造函数,该构造函数需要Bold
,Paragraph
等。
答案 2 :(得分:1)
您可以实现一个包装类,该类公开Inlines
属性并通过反射委托给包含的对象。
决定是否要在构造函数中或在尝试引用它时验证包装对象确实有Inlines
答案 3 :(得分:1)
使用Adapter Pattern,为每个要处理的类编写一个类,将它们有效地包装在实现公共层的层中。
为了使类可被发现,我会使用反射,用它们处理的类的属性标记每个这样的类,即:
[InlineContainerAdapter(typeof(SpecificClass1))]
public class WrapSpecificClass1 : IInlineContainer
并使用反射来查找它们。
这会给你带来好处:
如果这听起来像一个有趣的解决方案,请发表评论,我会提出一个完整的示例。
答案 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
}