使用LINQ创建List ,其中T:someClass

这与我的C#Generic List转换为实现List 的类的先前问题有关

我有以下代码:

public abstract class DataField { public string Name { get; set; } } public class DataField : DataField { public T Value { get; set; } } public static List ConvertXML(XMLDocument data) { result = (from d in XDocument.Parse(data.OuterXML).Root.Decendendants() select new DataField { Name = d.Name.ToString(), Value = d.Value }).Cast().ToList(); return result; } 

这工作但我希望能够修改LINQ查询的选择部分是这样的:

 select new DataField { Name = d.Name.ToString(), Value = d.Value } 

这只是一种糟糕的方法吗? 可能吗? 有什么建议?

这是一个可行的解决方案:(您必须为Type属性指定完全限定的类型名称,否则您必须以某种方式配置映射…)

我使用了动态关键字,如果你没有C#4,你可以使用reflection来设置值…

 public static void Test() { string xmlData = "Value1324"; List dataFieldList = DataField.ConvertXML(xmlData); Debug.Assert(dataFieldList.Count == 2); Debug.Assert(dataFieldList[0].GetType() == typeof(DataField)); Debug.Assert(dataFieldList[1].GetType() == typeof(DataField)); } public abstract class DataField { public string Name { get; set; } ///  /// Instanciate a generic DataField given an XElement ///  public static DataField CreateDataField(XElement element) { //Determine the type of element we deal with string elementTypeName = element.Attribute("Type").Value; Type elementType = Type.GetType(elementTypeName); //Instanciate a new Generic element of type: DataField dynamic dataField = Activator.CreateInstance(typeof(DataField<>).MakeGenericType(elementType)); dataField.Name = element.Name.ToString(); //Convert the inner value to the target element type dynamic value = Convert.ChangeType(element.Value, elementType); //Set the value into DataField dataField.Value = value; return dataField; } ///  /// Take all the descendant of the root node and creates a DataField for each ///  public static List ConvertXML(string xmlData) { var result = (from d in XDocument.Parse(xmlData).Root.DescendantNodes().OfType() select CreateDataField(d)).ToList(); return result; } } public class DataField : DataField { public T Value { get; set; } } 

你不能在C#中轻松做到这一点。 generics类型参数必须在编译时指定。 你可以使用reflection来做其他事情

  int X = 1; Type listype = typeof(List<>); Type constructed = listype.MakeGenericType( X.GetType() ); object runtimeList = Activator.CreateInstance(constructed); 

这里我们刚刚创建了一个List 。 你可以用你的类型做到这一点

generics类的不同实例实际上是不同的类。
DataFieldDataField根本不是同一个类(!)

这意味着,您无法在运行时定义generics参数,因为必须在编译期间确定它。

我会说这是一种糟糕的做法。 实际上,即使在解析XML文件之后,您也不会知道自己拥有哪种类型的“DataField”。 您也可以将它们解析为对象。

但是,如果你知道你只有x个类型,你可以这样做:

 var Dictionary> myFactoryMaps = { {"Type1", (name, value) => { return new DataField(name, Type1.Parse(value); } }, {"Type2", (name, value) => { return new DataField(name, Type2.Parse(value); } }, }; 

Termit的答案肯定很棒。 这是一个小变种。

  public abstract class DataField { public string Name { get; set; } } public class DataField : DataField { public T Value { get; set; } public Type GenericType { get { return this.Value.GetType(); } } } static Func dfSelector = new Func( e => { string strType = e.Attribute( "type" ).Value; //if you dont have an attribute type, you could call an extension method to figure out the type (with regex patterns) //that would only work for struct Type type = Type.GetType( strType ); dynamic df = Activator.CreateInstance( typeof( DataField<>).MakeGenericType( type ) ); df.Name = e.Attribute( "name" ).Value; dynamic value = Convert.ChangeType( e.Value , type ); df.Value = value; return df; } ); public static List ConvertXML( string xmlstring ) { var result = XDocument.Parse( xmlstring ) .Root.Descendants("object") .Select( dfSelector ) .ToList(); return result; } static void Main( string[] args ) { string xml = "HelloWorld!324"; List dfs = ConvertXML( xml ); } 

你可以通过reflection创建generics类型

  var instance = Activator.CreateInstance( typeof(DataField) .MakeGenericType(Type.GetType(typeNameFromAttribute) ); // and here set properties also by reflection 

@Termit和@Burnzy提出了涉及工厂方法的好解决方案。

问题在于,您正在使用一堆额外的逻辑(更多测试,更多错误)来加载您的解析例程,以获得可疑的返回。

另一种方法是使用带有类型读取方法的简化的基于字符串的DataField – 这个问题的最佳答案。

类型值方法的实现,它很好但只适用于值类型(不包括字符串但包含DateTimes):

 public T? TypedValue() where T : struct { try { return (T?) Convert.ChangeType(this.Value, typeof(T)); } catch { return null; } } 

我假设您希望使用类型信息来执行操作,例如动态地将用户控件分配给字段,validation规则,正确的SQL类型以保持持久性等。

我做了很多这样的事情,看起来有点像你的。

在一天结束时,您应该从代码中分离元数据 – @ Burnzy的答案选择基于元数据的代码(DataField元素的“type”属性),这是一个非常简单的例子。

如果您正在处理XML,XSD是一种非常有用且可扩展的元数据forms。

至于存储每个字段数据的内容 – 使用字符串,因为:

  • 它们是可以为空的
  • 他们可以存储部分值
  • 他们可以存储无效值(告诉用户将他们的行为排序更透明)
  • 他们可以存储列表
  • 特殊情况不会侵入不相关的代码,因为没有
  • 学习正则表达式,validation,快乐
  • 你可以很容易地将它们转换为更强大的类型

我发现开发这样的小框架是非常有益的 – 这是一种学习经验,你会更多地了解用户体验以及从中进行建模的现实。

有四组测试用例我建议你先解决:

  • 日期,时间,时间戳(我称之为DateTime),期间(Timespan)
    • 特别是,请确保您测试的客户端具有不同的服务器位置。
  • 列表 – 多选外键等
  • 空值
  • 无效输入 – 这通常涉及保留原始值

使用字符串极大地简化了这一切,因为它允许您在框架内清楚地划分职责。 考虑在通用模型中进行包含列表的字段 – 它会很快变得毛茸茸,并且很容易在几乎每种方法中都有一个特殊的列表。 有了弦乐,降压就停在了那里。

最后,如果你想要一个坚实的实现这种东西而不必做任何事情,考虑DataSets – 我知道的旧学校 – 他们会做各种你不会想到的美妙事情,但你必须使用RTFM。

这个想法的主要缺点是它与WPF数据绑定不兼容 – 尽管我的经验是现实与WPF数据绑定不兼容。

我希望我正确地解释你的意图 – 祝你好运:)

不幸的是,例如CC之间没有inheritance关系。 但是,您可以从常见的非generics类inheritance,除此之外还可以实现通用接口。 这里我使用显式接口实现,以便能够声明类型为对象的Value属性,以及更具体的类型化Value属性。 值是只读的,只能通过类型化的构造函数参数进行分配。 我的结构并不完美,但是类型安全并且不使用reflection。

 public interface IValue { T Value { get; } } public abstract class DataField { public DataField(string name, object value) { Name = name; Value = value; } public string Name { get; private set; } public object Value { get; private set; } } public class StringDataField : DataField, IValue { public StringDataField(string name, string value) : base(name, value) { } string IValue.Value { get { return (string)Value; } } } public class IntDataField : DataField, IValue { public IntDataField(string name, int value) : base(name, value) { } int IValue.Value { get { return (int)Value; } } } 

然后可以使用抽象基类DataField将该列表声明为通用参数:

 var list = new List(); switch (fieldType) { case "string": list.Add(new StringDataField("Item", "Apple")); break; case "int": list.Add(new IntDataField("Count", 12)); break; } 

通过界面访问强类型字段:

 public void ProcessDataField(DataField field) { var stringField = field as IValue; if (stringField != null) { string s = stringField.Value; } } 

虽然其他问题主要提出了将XML元素转换为generics类实例的优雅解决方案,但我将讨论将DataField类建模为类似DataField <[type in attribute in Attribute]的通用方法的结果。 XML元素]>

在列表中选择DataField实例后,您要使用这些字段。 她的多态性发挥作用! 您希望迭代您的DataFields以统一的方式对待它们。 使用generics的解决方案通常最终会出现奇怪的开关/如果狂欢,因为没有简单的方法可以根据c#中的generics类型关联行为。

您可能已经看到过这样的代码(我正在尝试计算所有数字DataField实例的总和)

 var list = new List() { new DataField() {Name = "int", Value = 2}, new DataField() {Name = "string", Value = "stringValue"}, new DataField() {Name = "string", Value = 2f}, }; var sum = 0.0; foreach (var dataField in list) { if (dataField.GetType().IsGenericType) { if (dataField.GetType().GetGenericArguments()[0] == typeof(int)) { sum += ((DataField) dataField).Value; } else if (dataField.GetType().GetGenericArguments()[0] == typeof(float)) { sum += ((DataField)dataField).Value; } // .. } } 

这段代码完全乱七八糟!

让我们尝试使用您的generics类型DataField的多态实现,并添加一些方法Sum ,它接受旧的some并返回(可能已修改的)新总和:

 public class DataField : DataField { public T Value { get; set; } public override double Sum(double sum) { if (typeof(T) == typeof(int)) { return sum + (int)Value; // Cannot really cast here! } else if (typeof(T) == typeof(float)) { return sum + (float)Value; // Cannot really cast here! } // ... return sum; } } 

您可以想象您的迭代代码现在变得更加清晰,但您仍然在代码中使用了这个奇怪的switch / if语句。 这就是重点:generics在这里没有帮助你在错误的地方使用错误的工具。 generics是在C#中设计的,它为您提供编译时类型安全性,以避免潜在的不安全的转换操作。 它们还增加了代码的可读性,但这不是这里的情况:)

我们来看看多态解决方案:

 public abstract class DataField { public string Name { get; set; } public object Value { get; set; } public abstract double Sum(double sum); } public class IntDataField : DataField { public override double Sum(double sum) { return (int)Value + sum; } } public class FloatDataField : DataField { public override double Sum(double sum) { return (float)Value + sum; } } 

我想你不需要太多的幻想来想象你的代码的可读性/质量有多大增加。

最后一点是如何创建这些类的实例。 只需使用一些约定TypeName +“DataField”和Activator:

 Activator.CreateInstance("assemblyName", typeName); 

简短版本

generics不适合您的问题,因为它不会为DataField实例的处理增加价值。 使用多态方法,您可以轻松使用DataField的实例!

这并不是不可能的,因为你可以用reflection做到这一点。 但这不是仿制药的设计目标,也不是应该如何完成的。 如果你打算使用reflection来制作generics类型,你可以根本不使用generics类型,只需使用以下类:

 public class DataField { public string Name { get; set; } public object Value { get; set; } } 

您需要插入用于从XML确定数据类型的逻辑并添加您需要使用的所有类型,但这应该有效:

  result = (from d in XDocument.Parse(data.OuterXML).Root.Descendants() let isString = true //Replace true with your logic to determine if it is a string. let isInt = false //Replace false with your logic to determine if it is an integer. let stringValue = isString ? (DataField)new DataField { Name = d.Name.ToString(), Value = d.Value } : null let intValue = isInt ? (DataField)new DataField { Name = d.Name.ToString(), Value = Int32.Parse(d.Value) } : null select stringValue ?? intValue).ToList();