这与我之前的C# Generic List conversion to Class implementing List<T>
问题有关我有以下代码:
public abstract class DataField
{
public string Name { get; set; }
}
public class DataField<T> : DataField
{
public T Value { get; set; }
}
public static List<DataField> ConvertXML(XMLDocument data) {
result = (from d in XDocument.Parse(data.OuterXML).Root.Decendendants()
select new DataField<string>
{
Name = d.Name.ToString(),
Value = d.Value
}).Cast<DataField>().ToList();
return result;
}
但是我希望能够将LINQ查询的选择部分修改为:
select new DataField<[type defined in attribute of XML Element]>
{
Name = d.Name.ToString(),
Value = d.Value
}
这只是一种糟糕的做法吗?可能吗?有什么建议吗?
答案 0 :(得分:8)
这是一个可行的解决方案:(您必须为Type属性指定完全限定的类型名称,否则您必须以某种方式配置映射...)
我使用了动态关键字,如果您没有C#4,可以使用反射设置值...
public static void Test()
{
string xmlData = "<root><Name1 Type=\"System.String\">Value1</Name1><Name2 Type=\"System.Int32\">324</Name2></root>";
List<DataField> dataFieldList = DataField.ConvertXML(xmlData);
Debug.Assert(dataFieldList.Count == 2);
Debug.Assert(dataFieldList[0].GetType() == typeof(DataField<string>));
Debug.Assert(dataFieldList[1].GetType() == typeof(DataField<int>));
}
public abstract class DataField
{
public string Name { get; set; }
/// <summary>
/// Instanciate a generic DataField<T> given an XElement
/// </summary>
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<T>
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;
}
/// <summary>
/// Take all the descendant of the root node and creates a DataField for each
/// </summary>
public static List<DataField> ConvertXML(string xmlData)
{
var result = (from d in XDocument.Parse(xmlData).Root.DescendantNodes().OfType<XElement>()
select CreateDataField(d)).ToList();
return result;
}
}
public class DataField<T> : DataField
{
public T Value { get; set; }
}
答案 1 :(得分:5)
你不能在C#中轻松做到这一点。泛型类型参数必须在编译时指定。您可以使用反射来执行其他操作
int X = 1;
Type listype = typeof(List<>);
Type constructed = listype.MakeGenericType( X.GetType() );
object runtimeList = Activator.CreateInstance(constructed);
这里我们刚刚创建了一个List&lt; int&gt;。你可以用你的类型
来做答案 2 :(得分:4)
泛型类的不同实例实际上是不同的类
即DataField<string>
和DataField<int>
根本不是同一个类(!)
这意味着,您无法在运行时定义泛型参数,因为必须在编译期间确定它。
答案 3 :(得分:4)
我会说这是一种糟糕的做法。实际上,即使在解析XML文件之后,您也不会知道自己拥有哪种类型的“DataFields”。您也可以将它们解析为对象。
但是,如果你知道你只有x种类型,你可以这样做:
var Dictionary<string, Func<string, string, DataField>> myFactoryMaps =
{
{"Type1", (name, value) => { return new DataField<Type1>(name, Type1.Parse(value); } },
{"Type2", (name, value) => { return new DataField<Type2>(name, Type2.Parse(value); } },
};
答案 4 :(得分:4)
Termit的回答当然很棒。这是一个小变种。
public abstract class DataField
{
public string Name { get; set; }
}
public class DataField<T> : DataField
{
public T Value { get; set; }
public Type GenericType { get { return this.Value.GetType(); } }
}
static Func<XElement , DataField> dfSelector = new Func<XElement , DataField>( 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<DataField> ConvertXML( string xmlstring )
{
var result = XDocument.Parse( xmlstring )
.Root.Descendants("object")
.Select( dfSelector )
.ToList();
return result;
}
static void Main( string[] args )
{
string xml = "<root><object name=\"im1\" type=\"System.String\">HelloWorld!</object><object name=\"im2\" type=\"System.Int32\">324</object></root>";
List<DataField> dfs = ConvertXML( xml );
}
答案 5 :(得分:3)
您可以通过反射创建泛型类型
var instance = Activator.CreateInstance( typeof(DataField)
.MakeGenericType(Type.GetType(typeNameFromAttribute) );
// and here set properties also by reflection
答案 6 :(得分:2)
@Termit和@Burnzy提出了涉及factory methods的好解决方案。
问题在于你正在用一堆额外的逻辑(更多的测试,更多的错误)加载你的解析例程,以获得可疑的回报。
另一种方法是使用带有类型化读取方法的简化的基于字符串的DataField - this问题的最佳答案。
类型值方法的实现,它很好但只适用于值类型(不包括字符串但包含DateTimes):
public T? TypedValue<T>()
where T : struct
{
try { return (T?) Convert.ChangeType(this.Value, typeof(T)); }
catch { return null; }
}
我假设您希望使用类型信息来执行操作,例如动态地将用户控件分配给字段,验证规则,正确的SQL类型以保持持久性等。
我做了很多这样的事情,看起来有点像你的。
在一天结束时,您应该从代码中分离元数据 - @ Burnzy的答案选择基于元数据的代码(DataField元素的“type”属性),这是一个非常简单的例子。
如果您正在处理XML,XSD是一种非常有用且可扩展的元数据形式。
至于存储每个字段数据的内容 - 使用字符串,因为:
我发现开发这样的小框架非常有意义 - 这是一种学习经验,你会更多地了解用户体验以及从中进行建模的现实。
我建议您首先解决四组测试用例:
使用字符串可以极大地简化这一切,因为它可以让您在框架内清楚地划分职责。考虑在通用模型中进行包含列表的字段 - 它会很快变得毛茸茸,并且很容易在几乎每种方法中都有一个特殊的列表。有了弦乐,降压就会停止。
最后,如果你想要一个可靠的实现这种东西而不必做任何事情,考虑DataSets - 我知道的旧学校 - 他们会做各种你不会想到但你会做的好事必须RTFM。
这个想法的主要缺点是它与WPF数据绑定不兼容 - 尽管我的经验是现实与WPF数据绑定不兼容。
我希望我能正确地解释你的意图 - 祝你好运:)
答案 7 :(得分:2)
不幸的是,例如C<T>
和C<string>
之间没有继承关系。
但是,您可以从常见的非泛型类继承,除此之外还可以实现通用接口。
这里我使用显式接口实现,以便能够声明类型为对象的Value属性,以及更具体的类型化Value属性。
值是只读的,只能通过类型化的构造函数参数进行分配。我的构造并不完美,但是类型安全且不使用反射。
public interface IValue<T>
{
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<string>
{
public StringDataField(string name, string value)
: base(name, value)
{
}
string IValue<string>.Value
{
get { return (string)Value; }
}
}
public class IntDataField : DataField, IValue<int>
{
public IntDataField(string name, int value)
: base(name, value)
{
}
int IValue<int>.Value
{
get { return (int)Value; }
}
}
然后可以使用抽象基类DataField
将该列表声明为通用参数:
var list = new List<DataField>();
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<string>;
if (stringField != null) {
string s = stringField.Value;
}
}
答案 8 :(得分:2)
虽然其他问题主要提出了将XML元素转换为泛型类实例的优雅解决方案,但我将讨论将DataField类建模为类似 DataField&lt;的通用方法的后果。 [XML元素属性中定义的类型]&gt; 。
在列表中选择DataField实例后,您要使用这些字段。她的多态性开始发挥作用!您希望迭代DataFields以统一的方式对待它们。使用泛型的解决方案通常最终会出现奇怪的开关/如果狂欢,因为没有简单的方法可以根据c#中的泛型类型关联行为。
您可能已经看到过这样的代码(我正在尝试计算所有数字DataField实例的总和)
var list = new List<DataField>()
{
new DataField<int>() {Name = "int", Value = 2},
new DataField<string>() {Name = "string", Value = "stringValue"},
new DataField<float>() {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<int>) dataField).Value;
}
else if (dataField.GetType().GetGenericArguments()[0] == typeof(float))
{
sum += ((DataField<float>)dataField).Value;
}
// ..
}
}
此代码完全混乱!
让我们尝试使用您的泛型类型DataField进行多态实现,并向其中添加一些方法 Sum ,接受旧的some并返回(可能已修改的)新总和:
public class DataField<T> : 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语句。这就是重点:泛型在这里没有帮助你在错误的地方使用错误的工具。泛型是在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);
短版:
泛型不适合您的问题,因为它不会为DataField实例的处理增加价值。使用多态方法,您可以使用DataField的实例轻松工作。
答案 9 :(得分:1)
这并不是不可能,因为你可以用反射做到这一点。但这不是仿制药的设计目标,也不是应该的完成方式。如果你打算使用反射来制作泛型类型,你也可以根本不使用泛型类型,只需使用下面的类:
public class DataField
{
public string Name { get; set; }
public object Value { get; set; }
}
答案 10 :(得分:1)
您需要插入用于从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<string>
{
Name = d.Name.ToString(),
Value = d.Value
} : null
let intValue = isInt ? (DataField)new DataField<int>
{
Name = d.Name.ToString(),
Value = Int32.Parse(d.Value)
} : null
select stringValue ?? intValue).ToList();