简体   繁体   中英

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

So I've been searching and searching for the solution to this problem, and I'm painfully aware that perhaps I just don't know how to ask the question in the right way to find an answer, so I'm more than happy, if there's an existing solution, to be pointed to the relevant article (or even just to get a better understanding/grasp of how to say what it is that I'm trying to find out!)

That being said, I have an abstract base class for managing / handling external, XML-based source data in generic ways and to act as the foundation for a ton of derived classes that sit on top of it and contexualize this raw data into use-specific formats.

In another class, intended to be an abstract foundation for a series of other classes whose jobs are to manage data that's stored in the first set of classes I described. In this second foundational class, have a method into which I want to be able to pass every and any possible class that is derived from my abstract base data class without that method having any foreknowledge of what the incoming class actually is (other than that it must be derived from the aforementioned archetype data classes).

This is obviously pretty confusing and is a difficult thing to try and explain/describe in words (thus my problem trying to ask the right question to find an answer) so below is a (greatly) pared-down code sample that I hope might better illustrate what I'm trying to say...

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

So, in addition to the things that I've mentioned in comments above, the compiler also rejects that call to base.DisplayContents<MySpecificData>(title); with the error:

Error CS0310 'MySpecificData' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'Y' in the generic type or method 'SomeOtherClassFoundation.DisplayContents(string)'

Clearly MySpecificData HAS a public, parameterless constructor, and, while it is DERIVED from an abstract base type, it is not, itself, one...

Also, there are tons of problems with the dynamic implementation of the derived classes within the DisplayContents(string) function, from not recognizing the methods requested, to attempting to call the prototype methods instead of the overriding ones...

This has been killing me for three days now, and it's pretty obvious that stuff is happening here that I don't understand, so any pointers, insights, suggestions and/or clarifications would be greatly appreciated!

I didn't get what you actually want to do. But reading your code, I made some changes that seems can help:

  1. Add a constraint to PrototypeDataGroup :

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

  2. Add a constraint to DisplayContents method:

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

  3. Make the call to DisplayContents method as below:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM