【问题标题】:Using LINQ to create a List<T> where T : someClass<U>使用 LINQ 创建一个 List<T> where T : someClass<U>
【发布时间】:2011-12-31 12:01:52
【问题描述】:

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

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

【问题讨论】:

  • 我觉得你永远不会知道你有什么 DataField 的子类,所以为什么不直接使用 class DataField{string name; object value;} 呢?
  • 我真的不想将所有值都引用为对象。如果我要采取类似的方法,使用 class DataField{string name; 似乎更容易。字符串类型;字符串值}。
  • 当然可以,但是每次使用前您都必须解析数据。如果您将数据预先解析为对象,然后将它们存储在数据字段中,则可以在准备使用它们时进行转换。这就是 ADO.NET 对 DataReader 所做的事情(尽管 DataReader 会延迟解析的执行,直到需要列值为止)。例如,if(myField.Value is DateTime { /* do that date thing */ }if(myField.Name == "importantDateThing") { var date = (DateTime)myField.Value; /* do importanty date thing */ }
  • @xixonia....是的,我明白这一点。但是这样的铸造对我来说感觉不对。我知道类型并且有能力构建不同类型的列表。
  • 事情是这样的,你还是要投。您要么检查 DataField 的类型并进行强制转换,要么检查对象的类型并进行强制转换。

标签: c# generics linq-to-xml


【解决方案1】:

泛型类的不同实例实际上是不同的类。
IE。 DataField&lt;string&gt;DataField&lt;int&gt; 根本不是同一个类(!)

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

【讨论】:

  • 这并不完全正确。您可以在运行时使用反射创建泛型类型。你说的更像是 C++ 模板的行为方式。
  • 另外,DataField 和DataField 仍然可以继承同一个父类,因此它们可以实现抽象方法,通过父类引用来调用。说的很糟糕,但你明白了。
【解决方案2】:

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

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

这里我们刚刚创建了一个列表。你可以用你的类型来做

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      这并非不可能,因为您可以通过反射来做到这一点。但这不是泛型的设计目的,也不是应该做的。如果你打算使用反射来制作泛型类型,你还不如根本不使用泛型类型,而只使用以下类:

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

      【讨论】:

        【解决方案5】:

        我会说这是一个糟糕的方法。实际上,即使在解析 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); }  },
        };
        

        【讨论】:

        • 永远不要低估抽象的力量。 :)
        【解决方案6】:

        您需要插入用于从 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();
        

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

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

            【讨论】:

              【解决方案9】:

              @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 元素的“类型”属性)选择代码,这是一个非常简单的示例。

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

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

              • 它们可以为空
              • 它们可以存储部分值
              • 他们可以存储无效值(让告诉用户整理他们的行为更加透明)
              • 他们可以存储列表
              • 特殊情况不会侵入无关代码,因为没有任何代码
              • 学习正则表达式、验证、快乐
              • 您可以非常轻松地将它们转换为更强大的类型

              我发现开发这样的小框架非常有益 - 这是一种学习体验,您将进一步了解用户体验以及从中建模的现实。

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

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

              使用字符串可以大大简化这一切,因为它可以让您在框架内清楚地划分职责。考虑在您的通用模型中处理包含列表的字段 - 它很快就会变得毛茸茸,并且很容易在几乎每种方法中都出现列表的特殊情况。有了字符串,责任就止步于此了。

              最后,如果你想在不需要做太多事情的情况下可靠地实现这类东西,请考虑 DataSets - 我知道的老派 - 他们会做各种你不会想到的美妙的事情,但你必须这样做实时调频。

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

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

              【讨论】:

                【解决方案10】:

                不幸的是,C&lt;T&gt;C&lt;string&gt; 之间没有继承关系。 但是,您可以从一个通用的非泛型类继承,除此之外还可以实现一个泛型接口。 在这里,我使用显式接口实现,以便能够声明类型为对象的 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;
                    }
                }
                

                【讨论】:

                  【解决方案11】:

                  虽然其他问题大多提出了将 XML 元素转换为泛型类实例的优雅解决方案,但我将处理采用这种方法将 DataField 类建模为像 DataField.

                  将您的 DataField 实例选择到列表中后,您要使用这些字段。她的多态性开始发挥作用!您想迭代您的 DataFields 并以统一的方式对待它们。使用泛型的解决方案通常以奇怪的 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,该方法接受旧的一些并返回(可能已修改的)新总和:

                  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 的实例!

                  【讨论】:

                    猜你喜欢
                    • 2011-10-27
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多