使用LINQ创建一个列表<其中T: someClass<

本文关键字:列表 其中 someClass 一个 创建 LINQ 使用 | 更新日期: 2023-09-27 18:15:13

这与我之前在实现List

中将c#泛型列表转换为类的问题有关。

我有以下代码:

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
}

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

使用LINQ创建一个列表<其中T: someClass<

这是一个有效的解决方案:(您必须为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; }
}

在c#中不容易做到这一点。泛型类型参数必须在编译时指定。您可以使用反射来做其他事情

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

这里我们刚刚创建了一个List。你可以通过你的type

一个泛型类的不同实例实际上是不同的类。
DataField<string>DataField<int>根本不是同一类(!)

这意味着您不能在运行时定义泛型参数,因为它必须在编译时确定。

我想说这是一个糟糕的方法。实际上,即使在解析XML文件之后,您也不知道自己拥有什么类型的"datafield"。您不妨将它们解析为对象。

然而,如果你知道你只会有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); }  },
};

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 );
        }

您可以通过反射创建泛型类型

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

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

这样做的问题是,您正在为可疑的返回加载一堆额外的逻辑(更多的测试,更多的错误)的解析例程。

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

一个类型值方法的实现,它很好,但只适用于值类型(不包括字符串,但包括日期时间):

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),周期(时间跨度)
      特别是
    • ,确保您测试的服务器位置与客户端位置不同。
  • 列表-多选择外键等
  • null值
  • 无效输入-这通常涉及保留原始值

使用字符串极大地简化了这一切,因为它允许您在框架内清楚地划分职责。想想在你的通用模型中处理包含列表的字段——它很快就会变得很麻烦,而且很容易在几乎每个方法中都以列表的特殊情况而告终。对于字符串,责任止于此。

最后,如果你想要在不做任何事情的情况下实现这类东西,考虑一下DataSets——我知道它是老派的——它们做了各种你意想不到的奇妙的事情,但你必须使用RTFM。

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

我希望我正确地理解了你的意图-祝你好运:)

遗憾的是,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;
    }
}

虽然其他问题大多提出了将XML元素转换为泛型类实例的优雅解决方案,但我将处理采用将DataField类建模为泛型的方法的后果,如DataField<[在XML Element的属性中定义的类型]>

在列表中选择DataField实例后,您希望使用这些字段。她的多态性开始发挥作用了!您希望迭代您的datafield并以统一的方式处理它们。使用泛型的解决方案通常会以奇怪的switch/if告终,因为在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它接受旧的一些并返回(可能修改)新的Sum:

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实例一起工作 !

这不是不可能的,因为您可以通过反射来做到这一点。但这不是泛型的设计目的,也不是应该如何完成的。如果您打算使用反射来创建泛型类型,那么您还不如根本不使用泛型类型,而只使用以下类:

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<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();