如何将匿名类型作为参数传递?

181

我怎么将匿名类型作为参数传递给其他函数?考虑以下示例:

var query = from employee in employees select new { Name = employee.Name, Id = employee.Id };
LogEmployees(query);

query 变量在这里没有强类型。我应该如何定义我的 LogEmployees 函数来接受它?

public void LogEmployees (? list)
{
    foreach (? item in list)
    {

    }
}

换句话说,我应该使用什么东西代替?标记。


1
更好的不同的重复问题,涉及传递参数而不是返回数据:http://stackoverflow.com/questions/16823658/passing-linq-select-query-to-the-method?lq=1 - Rob Church
11个回答

219

我认为你应该为这个匿名类型创建一个类。在我看来,这是最明智的做法。但如果你真的不想这样做,你可以使用动态类型:

public void LogEmployees (IEnumerable<dynamic> list)
{
    foreach (dynamic item in list)
    {
        string name = item.Name;
        int id = item.Id;
    }
}

请注意,这并不是强类型的。因此,例如当“Name”更改为“EmployeeName”时,只有在运行时才会知道存在问题。


我将此标记为正确答案,因为使用了 dynamic。这对我来说真的很方便。谢谢 :) - Saeed Neamati
2
我同意一旦数据开始传递,更加结构化的方式可能/通常应该被优先考虑,以避免引入难以找到的错误(你会绕过类型系统)。然而,如果你想要找到一个妥协,另一种方法就是简单地传递一个通用的Dictionary。现在C#字典初始化器非常方便使用。 - Jonas
有些情况下,您需要一种通用的实现方式,而传递硬类型意味着可能需要切换或工厂实现,这会导致代码膨胀。如果您遇到真正动态的情况,并且不介意使用一些反射来处理接收到的数据,那么这是完美的解决方案。感谢@Tim S.提供的答案。 - Larry Smith

51
你可以这样做:
public void LogEmployees<T>(List<T> list) // Or IEnumerable<T> list
{
    foreach (T item in list)
    {

    }
}

...但是你不能对每个项目都做很多事情。你可以调用ToString,但你将无法直接使用(比如)NameId


2
除非您可以在第一行的末尾使用“where T:某些类型”来缩小类型。但是,在那时,期望某种常见接口类型比期望接口更有意义。 :) - CassOnMars
13
但是,您无法在匿名类型上使用“where T:某个类型”,因为它们不实现任何接口。 - Jon Skeet
1
@Jon Skeet:说得好,今天早上我的大脑不够用。 - CassOnMars
1
@JonSkeet。如果T是匿名类型,您是否可以使用反射仍然访问/设置属性呢?我想到了一个情况,即某人编写了一个“Select * from”语句,并使用匿名(或定义的)类来定义查询结果中哪些列映射到匿名对象上具有相同命名的属性。 - C. Tewalt
@matrixugly:是的,但在这种情况下,您根本不需要它是通用的 - 只需接受一个IList即可。 - Jon Skeet
显示剩余6条评论

21

很遗憾,你尝试的操作是不可能的。在底层,查询变量的类型被定义为一个匿名类型的 IEnumerable。由于无法在用户代码中表示匿名类型名称,因此无法将其作为函数的输入参数。

最好的方法是创建一个类型,并将其用作查询的返回值,然后将其传递到函数中。例如:

struct Data {
  public string ColumnName; 
}

var query = (from name in some.Table
            select new Data { ColumnName = name });
MethodOp(query);
...
MethodOp(IEnumerable<Data> enumerable);

但在这种情况下,您仅选择了一个字段,因此直接选择该字段可能更容易。这将导致查询被类型化为该字段类型的 IEnumerable。在这种情况下,列名称。

var query = (from name in some.Table select name);  // IEnumerable<string>

我的例子只有一个,但大多数情况下会更多。你的答案已经起作用了(现在相当明显)。我只需要休息一下吃午饭来好好思考一下它;-) - Tony Trembath-Drake
FYI:合并自http://stackoverflow.com/questions/775387/how-can-i-pass-an-anonymous-type-to-a-function - Shog9
一个需要注意的地方是,当你创建一个合适的类时,Equals 的行为会发生变化。也就是说,你必须实现它。(我知道这个差异,但在重构期间仍然忘记了它。) - LosManos

15

如果参数类型不是object,则无法将匿名类型传递给非泛型函数。

public void LogEmployees (object obj)
{
    var list = obj as IEnumerable(); 
    if (list == null)
       return;

    foreach (var item in list)
    {

    }
}

匿名类型是用于方法内短期使用的。

来自 MSDN - 匿名类型

您无法将字段、属性、事件或方法的返回类型声明为匿名类型。同样,您也不能将方法、属性、构造函数或索引器的形式参数声明为匿名类型。要将匿名类型或包含匿名类型的集合作为参数传递给方法,可以将参数声明为 object 类型。但是,这样做会破坏强类型的目的。

(重点在我这里)


更新

您可以使用泛型来实现您想要的功能:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

4
如果你不能将匿名类型(或匿名类型的集合)传递给方法,那么整个LINQ会失败。你可以传递,只是该方法必须完全是泛型的,不能使用匿名类型的属性。 - Jon Skeet
2
使用 objectdynamic ;p - Marc Gravell
如果使用 "as" 进行强制转换,应先检查列表是否为空。 - Alex
"can" != "have to"。在匿名类型中使用object与使方法成为泛型不同,详见我的回答。 - Jon Skeet

11

"dynamic"这个词也可以用于这个目的。

var anonymousType = new { Id = 1, Name = "A" };

var anonymousTypes = new[] { new { Id = 1, Name = "A" }, new { Id = 2, Name = "B" };

private void DisplayAnonymousType(dynamic anonymousType)
{
}

private void DisplayAnonymousTypes(IEnumerable<dynamic> anonymousTypes)
{
   foreach (var info in anonymousTypes)
   {

   }
}

8
你可以使用以下技巧(将其转换为匿名类型)来使用泛型:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {
        var typedItem = Cast(item, new { Name = "", Id = 0 });
        // now you can use typedItem.Name, etc.
    }
}

static T Cast<T>(object obj, T type)
{
    return (T)obj;
}

8
通常,您可以使用泛型来实现这一点,例如:
MapEntToObj<T>(IQueryable<T> query) {...}

编译器应该在调用MapEntToObj(query)时推断出T。不太确定您想在方法内部执行什么操作,所以我无法确定这是否有用...问题是在MapEntToObj内部仍然无法命名T - 您只能:

  • 使用T调用其他通用方法
  • T使用反射进行操作

但除此之外,匿名类型很难操作-最重要的是它们是不可变的;-p

另一个技巧(在提取数据时)是同时传递选择器-即类似于:

Foo<TSource, TValue>(IEnumerable<TSource> source,
        Func<TSource,string> name) {
    foreach(TSource item in source) Console.WriteLine(name(item));
}
...
Foo(query, x=>x.Title);

1
学到了新东西,没想到匿名类型是不可变的! ;) - Annie Lagang
1
@AnneLagang 这实际上取决于编译器,因为它们是由编译器生成的。在 VB.NET 中,匿名类型可以是可变的。 - Marc Gravell
1
FYI:合并自http://stackoverflow.com/questions/775387/how-can-i-pass-an-anonymous-type-to-a-function - Shog9

2
要传递匿名类型,请考虑使用dynamic。下面展示了一个更长的示例和您可以使用的技术。例如,考虑在此处称TreadSafeDynamicObject为“CustomEmployee”,以使代码更有意义。该代码具有接受动态对象(您的匿名、可能嵌套的类)的构造函数,例如: var someCustomEmploye = new { IsIntern = false, EmployeeFacts = new { IsSenior = true, BirthDate = new DateTime(1960, 1, 1) } };
您可以使用下面显示的技术将someCustomEmployee转换为动态对象,例如将其传递到构造函数中,在我的代码中它将是:
dynamic someEmp = new ThreadSafeDynamicObject(someCustomEmployee);

一旦您将someEmp转换为正确的动态对象,您的LogEmployee函数可以例如将该对象序列化并记录它或以其他方式处理它(请注意,如果仅需要序列化匿名类实例,则不必通过将其转换为动态对象来进行操作)。

示例:

dynamic threadSafeToyota = new ThreadSafeDynamicObject(new {
 Make = "Toyota",
 Model = "CR-H",
 Propulsion = new {
    IsHybrid = true,
    UsesPetrol = true,
    ElectricMotor = true
    } 
});

这段代码接受一个动态对象,并使用私有方法'ToDictionary'将提供的匿名类实例的对象图转换为字典,作为设置动态对象属性的另一种替代方式。

请注意,我在这里添加了一些代码以提供线程安全性,用于设置和获取属性。

public class ThreadSafeDynamicObject : DynamicObject, IEnumerable<KeyValuePair<string, object>>
{   

    public ThreadSafeDynamicObject()
    {       
    }
    
    public ThreadSafeDynamicObject(dynamic members)
    {
        var membersDict = ToDictionary(members);        
        InitMembers(membersDict);   
    }

    private IDictionary<string, object> ToDictionary(object data)
    {
        var attr = BindingFlags.Public | BindingFlags.Instance;
        var dict = new Dictionary<string, object>();
        foreach (var property in data.GetType().GetProperties(attr))
        {
            if (property.CanRead)
            {
                dict.Add(property.Name, property.GetValue(data, null));
            }
        }
        return dict;
    }

    private void InitMembers(IDictionary<string, object> membersDict)
    {
        foreach (KeyValuePair<string, object> member in membersDict){
            _members.AddOrUpdate(member.Key, member.Value, (key, oldValue) => member.Value); 
        }
    }

    private readonly ConcurrentDictionary<string, object> _members = new();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return _members.TryGetValue(binder.Name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _members.AddOrUpdate(binder.Name, value, (key, oldvalue) => value); 
        return true;
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _members.Keys.ToList().AsReadOnly(); 
    }

    public override string ToString()
    {
        return JsonSerializer.Serialize(_members);
    }

    public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
    {
        return _members.GetEnumerator(); 
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _members.GetEnumerator(); 
    }
}

当在Linqpad 7中运行代码时,我得到了以下输出(我正在使用静态System.Console并在此处使用System.Dynamic):

WriteLine(threadSafe.ToString());
WriteLine(threadSafe.Make);
WriteLine(threadSafe.Model);
WriteLine(threadSafe.Propulsion.IsHybrid);
WriteLine(threadSafe.Propulsion.UsesPetrol);
WriteLine(threadSafe.Propulsion.ElectricMotor);

将匿名对象输出到字典并使用动态对象后的输出

这样做有几个优点。它支持嵌套级别,正如您在输出中所看到的,并且非常灵活。这里的关键是使用'ToDictionary'方法。此外,我们不必在.NET框架之外使用其他库,因此功能已内置。我没有检查此代码的所有变体,但至少支持匿名类型对象图的典型场景。

关键在于首先将匿名类型转换为字典,然后使用派生的DynamicObject的成员('fields'或'props')填充内部并发字典。

有几种解决方法:

  • 您可以进行装箱。例如,编写一个接受对象的方法,使用反射提取属性并记录属性及其值,例如:

    public void LogEmployees(object someCustomEmployee) { // .. }

  • 您可以像我的示例一样将匿名对象转换为动态对象

  • 除了装箱或转换为动态对象之外,您还可以通过序列化转换后的对象(包括装箱的对象或动态变量)来避免反射。


2

不要传递匿名类型,而是传递一个动态类型的List:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. 方法签名:DoSomething(List<dynamic> _dynamicResult)
  3. 调用方法:DoSomething(dynamicResult);
  4. 完成。

感谢Petar Ivanov


0

如果您知道您的结果实现了某个接口,您可以使用该接口作为数据类型:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接