[英]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.