使用LINQ创建List <t>,其中T:someClass <u> </u> </t>

时间:2011-11-17 16:02:30

标签: c# generics linq-to-xml

这与我之前的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
}

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

11 个答案:

答案 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是一种非常有用且可扩展的元数据形式。

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

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

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

我建议您首先解决四组测试用例:

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

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

最后,如果你想要一个可靠的实现这种东西而不必做任何事情,考虑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();