将方法上的泛型类型约束为从抽象泛型基类的任何变体形式派生的任何类

时间:2018-10-25 22:55:26

标签: c# generics inheritance

因此,我一直在寻找并解决该问题的解决方案,我痛苦地意识到,也许我只是不知道如何以正确的方式提出问题以找到答案,所以我如果有一个现有的解决方案,不胜高兴地指向相关文章(甚至只是为了更好地理解/掌握如何说出我想找出的含义!)

话虽这么说,但我有一个抽象基类,用于以通用方式管理/处理基于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);的调用,并显示以下错误:

  

错误CS0310“ MySpecificData”必须是具有公共无参数构造函数的非抽象类型,才能在通用类型或方法“ SomeOtherClassFoundation.DisplayContents(string)”中将其用作参数“ Y”

显然MySpecificData具有一个公共的,无参数的构造函数,尽管它是从抽象基本类型派生的,但它本身并不是一个...

DisplayContents(string)函数中派生类的动态实现也存在很多问题,从不认识所请求的方法到尝试调用原型方法而不是重写方法…… / p>

这已经使我丧命三天了,很明显这里发生了我不了解的事情,因此,任何指点,见解,建议和/或澄清都将不胜感激!

1 个答案:

答案 0 :(得分:0)

我没有得到您真正想要做的。但是阅读您的代码后,我做了一些似乎可以帮助您的更改:

  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);