【发布时间】:2018-10-25 22:55:26
【问题描述】:
所以我一直在寻找这个问题的解决方案,我痛苦地意识到,也许我只是不知道如何以正确的方式提出问题以找到答案,所以我非常高兴,如果有一个现有的解决方案,指向相关文章(或者甚至只是为了更好地理解/掌握如何说出我想要找出的内容!)
话虽如此,我有一个抽象基类,用于以通用方式管理/处理基于 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);
}
}
因此,除了我在上面的 cmets 中提到的内容之外,编译器还会拒绝对 base.DisplayContents<MySpecificData>(title); 的调用,并出现错误:
错误 CS0310“MySpecificData”必须是具有公共无参数构造函数的非抽象类型,才能将其用作泛型类型或方法“SomeOtherClassFoundation.DisplayContents(string)”中的参数“Y”
显然MySpecificData 有一个公共的、无参数的构造函数,虽然它是从抽象基类型派生的,但它本身并不是一个......
此外,DisplayContents(string) 函数中派生类的动态实现存在大量问题,从无法识别请求的方法到尝试调用原型方法而不是重写方法……
这已经让我死了三天,很明显这里发生了我不理解的事情,所以任何指示、见解、建议和/或澄清将不胜感激!
【问题讨论】:
-
哦,我尝试在该方法上使用
where Y : PrototypeDataGroup<>, new(),编译器吐出了奇怪的重言式错误:“T is T; Unexpected use of an unbound generic name.”。很明显这行不通…… -
为什么不试试
PrototypeDataGroup<T> where T : PrototypeDataClass, new()和DisplayContents<Y, T>(string title) where Y : PrototypeDataGroup<T>, new()? -
当我第一次开始尝试实现“where”并做了“where T : PrototypeDataClass”的事情时,它只接受 PrototypeDataClass 的类,而不接受它的任何派生类。显然,由于基类是抽象的,因此不可能有任何直接实例,这不是编译器关注的重点。由于我只想要 derivative 类,IDK 如何让 VS 理解呢?至于使用
:由于 T 已经是 Y 的一个现存的、必要的和不可分割的组成部分,这似乎是一种极其冗余/低效的解决方法...... -
在代码中 MySpecificData 有一个无参数的构造函数,但基本类型没有。当我定义一个错误就消失了。
-
此外,
DisplayContents<Y>(string)声明中“Y”的传递类型将是 派生自PrototypeDataGroup<T>的类,而不是它的实际实例。 .
标签: c# generics inheritance