Reputation: 8937
The System.Windows.Documents
namespace includes a number of classes with an Inlines
property of type InlineCollection
. For example, the Paragraph
, Bold
and Hyperlink
classes all have this property.
Each of these classes is decorated with 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.
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).
Edit 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:
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.
Upvotes: 3
Views: 499
Reputation: 56507
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);
}
}
Upvotes: 1
Reputation: 15545
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:
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. 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
.
Upvotes: 1
Reputation: 32112
Depending on your use-case, you could create a public Api that delegated its work to a private method that takes a dynamic
. This keeps the strong typing for your public Api and eliminates code duplication, even though it falls back to using dynamic
internally.
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
}
Upvotes: 0
Reputation: 391526
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.
Upvotes: 1
Reputation: 26668
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;
}
}
Upvotes: 2
Reputation: 1721
You could implement a wrapper class that exposes an Inlines
property and delegates via reflection to the contained object.
Decide if you want to validate that the wrapped object indeed has Inlines
in your constructor or when trying to reference it
Upvotes: 1
Reputation: 3417
You could always superclass them (e.g. InlineParagraph, InlineBold, etc) and have each of your superclasses implement an IInlineContainer interface like you suggested. Not the quickest or cleanest solution, but you at least have them all descending from the same interface.
Upvotes: 0
Reputation: 14478
If you didn't need to access it in a strongly-typed manner, but just without reflection, you could use 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.
Upvotes: 1