[英]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. 例如,
Paragraph
, Bold
和Hyperlink
类都具有此属性。
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. 另一种选择是定义一个包装器,它公开一个具有相同名称的属性,并且有一个重载的构造函数,它接受
Bold
, Paragraph
等。
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: 这会给你带来好处:
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.