c# - 将方法上的泛型类型约束为从抽象泛型基类的任何变体形式派生的任何类
问题描述
所以我一直在寻找和寻找这个问题的解决方案,我痛苦地意识到,也许我只是不知道如何以正确的方式提出问题来找到答案,所以我非常高兴,如果有现有的解决方案,请指向相关文章(或者甚至只是为了更好地理解/掌握如何说出我想要找出的内容!)
话虽如此,我有一个抽象基类,用于以通用方式管理/处理基于 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)
,从无法识别请求的方法,到尝试调用原型方法而不是重写方法......
这已经让我死了三天了,很明显这里发生了我不明白的事情,所以任何指示、见解、建议和/或澄清将不胜感激!
解决方案
我没有得到你真正想做的事情。但是阅读您的代码后,我做了一些似乎有帮助的更改:
添加一个约束到
PrototypeDataGroup
:internal abstract class PrototypeDataGroup<T> where T : PrototypeDataClass, new()
为方法添加约束
DisplayContents
:DisplayContents<Y,T>(string title) where Y : PrototypeDataGroup<T>, new() where T: PrototypeDataClass, new()
如下调用
DisplayContents
方法:base.DisplayContents<MySpecificDataGroup, MySpecificData>(title);
推荐阅读
- python - Python从图中获取所有路径
- python - 拟合高斯过程的后验样本与预测均值不相似
- makefile - 如果Makefile中foreach内部的条件如何
- spring-boot - 将 Docker 部署到 GCP App Engine 时,如何修复 http 500 错误网关?
- docker - Docker-compose 无效的卷规范和服务器名称
- jekyll - 通过 site.categories 访问时无法访问 Jekyll 帖子的自定义变量
- python - 如何将 y_true 作为 dict 传递给未更改的自定义损失函数?
- laravel - 如何在laravel中对关系结果进行分页
- api - 列出查看次数最多的页面 wikimedia 项目 (wikipedia) 有超过 1000 个结果
- c# - 在哪里存储额外的用户信息?