如何使用 Linq 和 XML 实现泛型方法方法

本文关键字:实现 泛型方法 方法 XML 何使用 Linq | 更新日期: 2023-09-27 18:36:43

好吧,我想我有一些重复的代码可以适合使用泛型。

我有两个不同的 Xml 文件,我打开、查询并作为绑定到 GridView 的集合返回。 这些集合是用 xml 中的数据填充的自定义类的列表。 每个网格视图都有其相应的自定义类。 目前我有两个,并说这些类的名称是XmlDataSource1和XmlDataSource2

下面是使用 XmlDataSource1 作为示例的当前工作示例。请注意,XmlDataSource1 对象的构造函数从查询中获取 XElements 并填充自身。没什么疯狂的。

GridView gv = new GridView();
gv.DataSource = GetXmlDataSource1(pathToXmlFile);
gv.DataBind();
public List<XmlDataSource1> GetXmlDataSource1(string pathToXmlFile)
{
   XDocument xml = XDocument.Load(pathToXmlFile);
   IEnumerable<XmlDataSource1> query = from s in xml.Descendants("NodeForXml1")
                                       select new XmlDataSource1(s);
   // Where clauses based on user inputs (deferred execution)
   query = query.Where(x => x.ID = SomeUserInputId);
   // More of these where clauses if they have inputs for them...
   // Convert to a List and return
   return query.ToList();
}

现在,要实现 GetXmlDataSource2() 方法,它大约 98% 相同。 当然,主要区别在于 linq 查询的选择部分创建 XmlDataSource2 对象的新实例、"NodeForXml2"后代目标节点,以及一些适用/不适用的 where 子句。

如何使这些 GetXmlDataSource# 方法成为通用方法? 理想情况下,我想按如下所述调用它,这是我尝试过的,但我无法让 linq 查询的选择部分调用正确数据对象的构造函数。

GridView gv1 = new GridView();
GridView gv2 = new GridView();
gv1.DataSource = GetXmlDataSource<XmlDataSource1>(pathToXmlFile);
gv2.DataSource = GetXmlDataSource<XmlDataSource2>(pathToXmlFile);
gv1.DataBind();
gv2.DataBind();
public List<T> GetXmlDataSource<T>(string pathToXmlFile)
{
   // The type of T in case I need it
   Type typeOfT = typeof(T);
   XDocument xml = XDocument.Load(pathToXmlFile);
   // How to make new XmlDataSource1 and 2 objects?? This statement doesn't work.
   IEnumerable<T> query = from s in xml.Descendants("NodeForXml1")
                          select new T(s);

   // How to return the IEnumerable query to a List of the T's?
   return query.ToList();
}

离我有多远? 我接近了吗?

如何使用 Linq 和 XML 实现泛型方法方法

一种解决方案可能是使用 Activator.CreateInstance

   IEnumerable<T> query = from s in xml.Descendants("NodeForXml1")
                                       select (T)Activator.CreateInstance(typeOfT, s);

但是要注意性能问题,这是Jon Skeet处理它的一篇很棒的帖子:

显然,这比召集代表要慢 - 可能是由于 尝试找到具有反射的可访问构造函数,并调用 它

因此,如果需要性能,最好将委托传递给GetXmlDataSource1方法,并使用它来在 Linq 查询中创建所需的实例。

关于您需要将 XmlDataSources 公共属性访问到GetXmlDataSource<T>方法中,您至少有 2 种解决方案:

1:创建一个包含公共属性的接口:

public interface IXmlDataSource
{
    string ID { get; set; }   
    string CommonProperty1 { get; set; }   
    string CommonProperty2 { get; set; }
}

这个将由你的XmlDataSources实现。这是一个典型的实现:

public class XmlDataSource1 : IXmlDataSource
{
    public string ID { get; set; }   
    public string CommonProperty1 { get; set }    
    public string CommonProperty2 { get; set }
    ... // the rest of your code
}

最后,约束T类型将授予您在需要这些属性时访问这些属性的权限query = query.Where(x => x.ID = SomeUserInputId);

public List<T> GetXmlDataSource<T>(string pathToXmlFile) where T : IXmlDataSource

2 : 委托也可以做到这一点,这是在这种情况下的典型调用:

GetXmlDataSource<XmlDataSource1>(pathToXmlFile, (query, result) =>
{ 
    return query.Select(e => new XmlDataSource1(e)).Where(x => x.YourProperty == value);
});

使用如下所示的GetXmlDataSource<T>方法签名:

public List<T> GetXmlDataSource<T>(string pathToXmlFile, Func<IEnumerable<XElement>, IEnumerable<T>> transform)
{
   // The type of T in case I need it
   Type typeOfT = typeof(T);
   XDocument xml = XDocument.Load(pathToXmlFile);
   IEnumerable<XElement> query = from s in xml.Descendants("NodeForXml1")
                                 select s;
   // Create and filter XmlDataSource1 instances thanks to the "transform" delegate
   return transform(query).ToList();
}

看起来你已经很接近了 - 一种选择是让调用者传入函数来创建实例:

public List<T> GetXmlDataSource<T>(string pathToXmlFile, 
                                   string elementName,     
                                   Func<XElement,T> factoryMethod)
{
...
   IEnumerable<T> query = from s in xml.Descendants(elementName)
                                       select factoryMethod(s);
...
}

然后来电者会说:

List<XmlDataSource1> list = GetXmlDataSource1(pathToXmlFile, 
                                              "NodeForXml1", 
                                              s => new XmlDataSource1(s))