如何使用 linq 从 var-source 获取值

Posted

技术标签:

【中文标题】如何使用 linq 从 var-source 获取值【英文标题】:how to get values from var-source with linq 【发布时间】:2012-10-07 10:56:43 【问题描述】:

我有一本字典:

Dictionary<int, Type> AllDrillTypes = new Dictionary<int, Type>()

  13,typeof(TCHEMISTRY),
  14,typeof(TDRILLSPAN)
;

其中 TCHEMISTRY 和 TDRILLSPAN 是类。然后我想从这样的一个类中获取行:

Type T = AllDrillTypes[13];
var LC = Activator.CreateInstance( typeof(List<>).MakeGenericType( T ) );
MethodInfo M = T.GetMethod("FindAll", BindingFlags.Public | BindingFlags.Static, null, new Type[]  , null);    
LC = M.Invoke(null, new object[]  );

所有这些代码都可以正常工作。之后我需要得到一些这样的行:

var LingLC = from obj in LC where obj.RunID == 1001 select obj;

但是这一行会导致错误:

"找不到源类型的查询模式的实现 '目的'。 '哪里'没有找到。”

这行代码有什么问题?

【问题讨论】:

您能指定您遇到的错误类型吗?这将有助于回答您的问题! 您确定要以这种方式使用反射吗? 错误文本是“找不到源类型'object'的查询模式的实现。找不到'Where'。” 如果您使用反射来创建通用对象,您如何期望您的编译器 知道将obj 解析为什么类型? =) 当你在 TChemistry 类上调用 FindAll 方法时,它返回什么?收藏?收藏的类型是什么?如果是 List,则必须在使用 Linq 之前将 LC 转换为 List 【参考方案1】:

即使不能更改类定义,也可以避免使用反射:

// Getter dictionary rather than type dictionary.
Dictionary<int, Func<IEnumerable<object>>> DrillTypeGetters =
    new Dictionary<int, Func<IEnumerable<object>>>()   
      
         13, () => TCHEMISTRY.FindAll().Cast<object>() ,
         14, () => TDRILLSPAN.FindAll().Cast<object>() 
    ;
Dictionary<int, Func<object, int>> IDGetters =
    new Dictionary<int, Func<object, int>>()
    
         13, o => ((TCHEMISTRY)o).RunID ,
         14, o => ((TDRILLSPAN)o).RunID 
    ;

IEnumerable<object> LC = DrillTypeGetters[13]();
IEnumerable<object> LingLC = 
    from obj in LC
    where IDGetters[13](obj) == 1001
    select obj;

或者您甚至可以打开13/14 并为每种类型运行一个完全不同的方法

if (choice == 13)
    IEnumerable<TCHEMISTRY> LingLC =
        TCHEMISTRY.FindAll().Where(tc => tc.RunID == 1001);
else if (choice == 14)
    IEnumerable<TDRILLSPAN> LingLC =
        TDRILLSPAN.FindAll().Where(td => td.RunID == 1001);

基本上,如果这两个类不共享任何共同的层次结构,您就无法编写任何共同的代码来处理它们。如果它们有很多 similar 属性,您可以像我的第一个示例中那样使用 getter 来提供一种方法来获取您正在处理的任何类型的类的相似属性。如果它们甚至没有相似的属性,请不要尝试编写共享代码。

【讨论】:

不幸的是我不能改变类的声明。 T 哈哈好吧。让我看看,你仍然应该能够做到一半而不用反思。【参考方案2】:

也许你可以将你的代码重写成这样,......以获得更安全的解决方案(不使用反射)。

void Main()

    var driller1 = new DrillerWhichYouCannotChange1();
    var driller2 = new DrillerWhichYouCannotChange2();

    var allDrillTypes = new Dictionary<int, IList<IDriller>>()
    
         13, new List<IDriller>()  new DrillerWhichYouCannotChange1Adapter(driller1)  ,
         14, new List<IDriller>()  new DrillerWhichYouCannotChange2Adapter(driller2)  ,
    ;

    Console.WriteLine(allDrillTypes[13][0].SomeCommonProperty); // prints 123
    Console.WriteLine(allDrillTypes[14][0].SomeCommonProperty); // prints 456


interface IDriller

    int SomeCommonProperty  get; 


class DrillerWhichYouCannotChange1Adapter : IDriller

    private DrillerWhichYouCannotChange1 inner;

    public DrillerWhichYouCannotChange1Adapter(DrillerWhichYouCannotChange1 inner)
    
        this.inner = inner;
    

    public int SomeCommonProperty  get  return this.inner.PropertyX;  


class DrillerWhichYouCannotChange2Adapter : IDriller

    private DrillerWhichYouCannotChange2 inner;

    public DrillerWhichYouCannotChange2Adapter(DrillerWhichYouCannotChange2 inner)
    
        this.inner = inner;
    

    public int SomeCommonProperty  get  return this.inner.PropertyY;  


class DrillerWhichYouCannotChange1

    public int PropertyX  get  return 123;  


class DrillerWhichYouCannotChange2

    public int PropertyY  get  return 456;  

编辑:如果您无法更改钻孔器类,您可以使用适配器模式为每个钻孔器创建一个适配器,实现IDiller

【讨论】:

这是个好方法,但在我的案例中,Driller1 和 Driller2 类是自动创建的,所以我无法更改它们的声明【参考方案3】:

您需要将 LC 转换为 FindAll 方法的返回类型。大致如下:

var genericList = ((List<TChemistry>) LC);
var LingLC = from obj in genericList where obj.RunID == 1001 select obj;

这是假设 FindAll 返回 TChemistry 的集合。

--编辑

如果您在运行时不知道类型是 TChemistry 还是 TDrillspan,则必须编写一个 if/else 的 switch 语句来转换为正确的类型。我宁愿让 TChemistry 和 TDrillSpan 扩展一个抽象类或接口,你可以直接转换为 List,你将始终拥有 RunId 属性。

public abstract class TAbstract

   public abstract int RunId get; set;


public class TChemistry : TAbstract

    public override int RunId get; set;


public class TDrillSpan : TAbstract

   public override int RunId get; set;


Type T = AllDrillTypes[13] as TAbstract;
var LC = Activator.CreateInstance( typeof(List<>).MakeGenericType( T ) );
MethodInfo M = T.GetMethod("FindAll", BindingFlags.Public | BindingFlags.Static, null, new Type[]  , null);    
LC = M.Invoke(null, new object[]  );

var genericList = ((List<TAbstract>) LC);
var LingLC = from obj in genericList where obj.RunID == 1001 select obj;

如果你不能改变类的声明,那么你只剩下丑陋的 if else:

var typeInfo = LC.GetType();
IEnumerable<T> genericList;
if (typeInfo == typeof(IEnumerable<TChemistry>)

    genericList = (List<TChemistry>) LC;
)
else if (typeInfo == typeof(IEnumerable<TDrillSpan>)

    genericList = (List<TDrillSpan>) LC;


var LingLC = from obj in genericList where obj.RunID == 1001 select obj;

【讨论】:

您需要对 List&lt;TChemistry&gt; 类型的新变量进行强制转换 - 将其强制转换并存储回 object 将无济于事。 没错.. 我需要早上喝咖啡! 这是一个很好的解决方案,但我不能更改类的声明【参考方案4】:

试试

IEnumerable returnedObjects = (IEnumerable)M.Invoke(null, new object[]  ) as IEnumerable;

然后遍历你的 ienumerable

foreach (object report in returnedObjects)

    // Use reflection to read properties or add to a new List<object> if you
    // need an ICollection<object>

而不是:

LC = M.Invoke(null, new object[]  );

【讨论】:

我觉得可以,但是代码行太多了。谢谢你。我会尝试使用它,然后我会回答 如你所愿,如你所愿 =) 此代码不起作用,错误文本为“foreach 语句无法对 'object' 类型的变量进行操作,因为 'object' 不包含 'GetEnumeartor' 的公共定义”

以上是关于如何使用 linq 从 var-source 获取值的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 LINQ 从对象列表中获取唯一的属性列表?

如何使用 LINQ 从列表中获取重复项? [复制]

如何使用 LINQ 的 AsQueryable() 从 MongoDB 的内部数组中获取数据?

如何使用linq从字典中获取嵌套键值对[重复]

如何使用LINQ从列表中获取重复项?

如何使用 LINQ 中的键对值从单个字段中获取重复数据?