簡體   English   中英

將方法上的泛型類型約束為從抽象泛型基類的任何變體形式派生的任何類

[英]Constrain generic type on a method to be any class that is derived from any variant form of an abstract generic base class

所以我一直在尋找解決這個問題的方法,我痛苦地意識到,也許我只是不知道如何以正確的方式提出問題以找到答案,所以我很高興,如果有現有的解決方案,請指向相關文章(甚至只是為了更好地理解/掌握如何說出我要找出的含義!)

話雖這么說,我有一個抽象基類,用於以通用方式管理/處理基於XML的外部源數據,並充當大量派生類的基礎,這些派生類位於其之上並將這些原始數據轉化為使用-具體格式。

在另一個類中,該類旨在作為一系列其他類的抽象基礎,這些類的工作是管理我描述的第一組類中存儲的數據。 在第二個基礎類中,有一個方法,我希望能夠將從我的抽象基礎數據類派生的每個可能的類傳遞給該方法,而該方法不知道傳入類的實際含義(除了它必須從上述原型數據類派生)。

這顯然很令人困惑,並且很難嘗試用語言解釋/描述(因此我的問題是試圖提出正確的問題以找到答案),因此下面是一個(精簡)的代碼示例,我希望可能會更好說明我想說的...

internal abstract class PrototypeDataClass
{
    // intention is to hold and manage one generic record of unknown data.
    protected List<KeyValuePair<string, string>> _data = new List<KeyValuePair<string,string>>();

    protected PrototypeDataClass(PrototypeDataClass source) =>
        this._data.AddRange(source._data.ToArray());

    // Expects an XmlNode containing field data...
    protected PrototypeDataClass(XmlNode source) 
    {
        XmlNodeCollection nodes = source.GetElementsByTagName("field");
        foreach (XmlNode node in nodes)
        {
            string key = XmlNode.Attributes["field"].Value,
                   value = XmlNode.InnerText;
            this.Add(key,value);
        }
    }

    public int Count => this._data.Count;
    public string this[string index]
    {
        get {
            int i = FindFieldByName(index);
            if ((i<0) || (i>=Count)) throw new ArgumentOutOfRangeException();
            return this[i];
        }
        set => this.Add(index,value);
    }

    protected int FindFieldByName(string fieldname)
    {
        int i=-1; while ((++i < Count) && !_data[i].Key.Equals(fieldname, StringComparison.InvariantCultureIgnoreCase));
        return (i < Count) ? i : -1;
    }

    public void Add(string key, string value) =>
        Add(new KeyValuePair<string,string>(key, value));

    public void Add(KeyValuePair newData)
    {
        int i = FindFieldByName(newData.Key);
        if (i<0)
            this._data.Add(newData);
        else
            this._data[i] = newData;
    }

    public abstract string FormattedDisplayLine();

    public static bool IsDerivedType(dynamic test) =>
        IsDerivedType(test.GetType());

    public static bool IsDerivedType(Type test) =>
        (test == typeof(Object)) || (test is null) ? false : 
            (test.BaseType == typeof(PrototypeDataClass)) ? true : IsDerivedType(test.BaseType);
}

// Problem 1: I would like the WHERE constraint here to facilitate using
//            only derivatives of PrototypeDataClass for T...
internal abstract class PrototypeDataGroup<T> where T : new()
{
    List<T> _data = new List<T>();

    protected PrototypeDataGroup()
    {
        // A clunky workaround to validate that the supplied generic type is
        // derived from my prototype...
        if (!PrototypeDataClass.IsDerivedType(typeof(T)))
           throw new Exception(typeof(T).Name + " is not derived from PrototypeDataClass.");
    }

    protected PrototypeDataGroup(T[] sourceData)
    {
        // Same clunky workaround...
        if (!PrototypeDataClass.IsDerivedType(typeof(T)))
           throw new Exception(typeof(T).Name + " is not derived from PrototypeDataClass.");

        foreach(T line in sourceData)
            this.Add(line);
    }

    protected PrototypeDataGroup(XmlDocument doc)
    {
        // Same clunky workaround...
        if (!PrototypeDataClass.IsDerivedType(typeof(T)))
           throw new Exception(typeof(T).Name + " is not derived from PrototypeDataClass.");

        XmlNodeCollection nodes = doc.GetElementsByTagName("rows");
        foreach (XmlNode node in nodes)
           this._data.Add(new PrototypeDataClass(node));         
    }

    public int Count => this._data.Count;
    public T this[int index] => this._data[index];

    public void Add(T item) =>
        this._data.Add(item);

    public abstract string[] FormattedDisplayLines();
}

internal class MySpecificData : PrototypeDataClass
{
    public MySpecificData() : base() { }

    public MySpecificData(PrototypeDataClass source) : base(source) { }

    public MySpecificData(XmlNode source) : base(source) { }

    public MySpecificData(KeyValuePair data) : base() =>
        this.Add(data);

    public MySpecificData(string key, string value) : base() =>
        this.Add(key, value);

    // Code to manage / present the generic data in MySpecific ways...
    public override string FormattedDisplayLine() =>
        _data["specificField1"] + ": " + _data["specificField2"];
}

internal class MySpecificDataGroup : PrototypeDataGroup<MySpecificData>
{
    public MySpecificDataGroup() : base() { }

    public MySpecificDataGroup(XmlDocument doc) : base(doc) { }

    public MySpecificDataGroup(MySpecificData[] source) : base(source) { }

    // present / manage the collection in MySpecific ways
    public override string[] FormattedDisplayLines()
    {
        List<string> lines = new List<string>();
        for(int i=0; i<Count; i++)
           lines.Add(new MySpecificData(this._data[i]).FormattedDisplayLine());
        return lines.ToArray();
    }
}

// This class's purpose is to provide the foundation for another set of
// classes that are designed to perform work using the data held in various
// derivations of PrototypeDataGroup<T>

internal abstract class SomeOtherClassFoundation
{
    XmlDocument _doc;

    public SomeOtherClassFoundation(XmlDocument source) =>
        this._doc = source;

    // Problem 2: would like to simply constrain Y here to EVERY/ANY
    // possible derivation of PrototypeDataGroup, but when I try that,
    //   i.e. "public void DisplayDoc<Y>(string title) where Y : PrototypeDataGroup, new()"
    // the compiler spits out an error that appears to demand that I 
    // pre-declare every individual allowable "Y" variant separately: 
    //   "Using the generic type 'PrototypeDataGroup<T>' requires at least 1 type arguments"
    // Soo: "public void DisplayDoc<Y>(string title) where Y : PrototypeDataGroup<MySpecificDataGroup>, PrototypeDataGroup<MyOtherSpecificDataGroup>, new()"
    // As there could ultimately be dozens of such derived classes, having
    // to maintain such a list manually is beyond daunting and seems
    // absurd. Is there no way to specify:
    //    "where Y : PrototypeDataGroup<>, new()" (for any/all values of '<>'?)
    protected void DisplayContents<Y>(string title) where Y : new()
    {
        // If I use "Y" here in lieu of "dynamic", the code won't even
        // compile as the compiler decides that it's absolutely impossible for
        // the Y type to have either the "Count" or "FormattedDisplayLines" methods.
        // By using "dynamic", it at least waits until runtime to make that evaluation
        // then reacts accordingly (though usually still badly)...
        dynamic work = new Y();
        if (work.Count > 0)
        {
            Console.WriteLn("Displaying " + work.Count.ToString() + " records:\r\n"+ title);
            foreach (string line in work.FormattedDisplayLines())
               Console.WriteLn(line);
        }       
    }
}

internal class SomeOtherClassForMySpecificData : SomeOtherClassFoundation
{
    public SomeOtherClassForMySpecificData(XmlDocument source) : base(source) { }

    public Show()
    {
        string title = "Specific Field 1 | Specific Field 2\r\n".PadRight(80,'=');
        base.DisplayContents<MySpecificData>(title);
    }
}

因此,除了我在上面的注釋中提到的內容之外,編譯器還拒絕了對base.DisplayContents<MySpecificData>(title);調用base.DisplayContents<MySpecificData>(title); 與錯誤:

錯誤CS0310“ MySpecificData”必須是具有公共無參數構造函數的非抽象類型,以便在通用類型或方法“ SomeOtherClassFoundation.DisplayContents(string)”中將其用作參數“ Y”

顯然, MySpecificData具有一個公共的,無參數的構造函數,盡管它是從抽象基本類型MySpecificData ,但它本身並不是一個……

此外, DisplayContents(string)函數中派生類的動態實現還存在很多問題,從無法識別所請求的方法到嘗試調用原型方法而不是重寫方法...

這已經使我喪命三天了,很明顯這里發生了我不理解的事情,因此,任何指點,見解,建議和/或澄清都將不勝感激!

我沒有得到您真正想要做的。 但是閱讀您的代碼后,我做了一些似乎可以幫助您的更改:

  1. PrototypeDataGroup添加一個約束:

    internal abstract class PrototypeDataGroup<T> where T : PrototypeDataClass, new()

  2. DisplayContents方法添加約束:

    DisplayContents<Y,T>(string title) where Y : PrototypeDataGroup<T>, new() where T: PrototypeDataClass, new()

  3. 如下調用DisplayContents方法:

    base.DisplayContents<MySpecificDataGroup, MySpecificData>(title);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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