簡體   English   中英

強類型屬性引用多個類,沒有通用接口(C#)

[英]Strongly-typed property reference to multiple classes with no common interface (C#)

System.Windows.Documents命名空間包含許多具有InlineCollection類型的Inlines屬性的類。 例如, ParagraphBoldHyperlink類都具有此屬性。

這些類中的每一個都使用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. ...

另一種選擇是定義一個包裝器,它公開一個具有相同名稱的屬性,並且有一個重載的構造函數,它接受BoldParagraph等。

您可以實現一個包裝類,該類公開Inlines屬性並通過反射委托給包含的對象。

確定是否要驗證包裝對象在構造函數中是否確實具有Inlines ,或者在嘗試引用它時

使用適配器模式 ,為您希望處理的每個類編寫一個類,有效地將它們包裝在實現公共層的層中。

為了使類可被發現,我將使用反射,用它們處理的類的屬性標記每個這樣的類,即:

[InlineContainerAdapter(typeof(SpecificClass1))]
public class WrapSpecificClass1 : IInlineContainer

並使用反射來找到它們。

這會給你帶來好處:

  1. 您不必處理動態或類似的解決方案
  2. 雖然你必須使用反射來查找類,但是一旦你創建了適配器,你實際執行的代碼是100%你的,手工編碼
  3. 您可以通過編寫不同的適配器,為那些沒有真正實現所需內容的類創建適配器,方法與其余類相同

如果這聽起來像一個有趣的解決方案,請留下評論,我會提出一個完整的例子。

這樣做的一種方法(除了使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM