在 C# 中返回匿名类型

Posted

技术标签:

【中文标题】在 C# 中返回匿名类型【英文标题】:Returning anonymous type in C# 【发布时间】:2012-04-21 19:58:55 【问题描述】:

我有一个返回匿名类型的查询,并且该查询在一个方法中。这个怎么写:

public "TheAnonymousType" TheMethod(SomeParameter)

  using (MyDC TheDC = new MyDC())
  
     var TheQueryFromDB = (....
                           select new  SomeVariable = ....,
                                        AnotherVariable = ....
                           ).ToList();

      return "TheAnonymousType";
    

【问题讨论】:

为什么要返回匿名类型?你怎么可能在其他地方使用这个结果? How can I return an anonymous type from a method?的可能重复 @Yuck 如果你返回 json 或 c# 类型无关紧要的东西怎么办 我不认为这个问题没有道理。我实际上需要多次这样做。当使用实体框架并且您希望在一个函数中进行查询并在多个地方使用结果时,这一点更为明显。当在屏幕上显示结果然后需要在报告中使用相同的结果或导出到 Excel 时,我经常需要这个。查询可能包含许多来自 UI 的过滤器等。您真的不想在多个地方创建相同的查询,否则当您想添加到结果时很容易不同步 Is there a way to return Anonymous Type from method?的可能重复 【参考方案1】:

你不能。

您只能返回object,或对象容器,例如IEnumerable<object>IList<object>

【讨论】:

dynamic。这使它更容易使用。 啊,好吧,所以你只能在方法中使用匿名类型,而不能作为返回值? @frenchie:是的,只在成员体内。如果你想返回它 - 让它成为一个众所周知的类型。 使用 dynamic 不是解决方案,匿名类型的字段不是公共的,它们是内部的。 @HansPassant 假设调用者在同一个程序集中,那么它仍然(有点)有用。值得一提的是,这些字段是公共的——类型是内部的。我通常认为你不应该返回匿名类型。【参考方案2】:

您可以返回 dynamic,这将为您提供匿名类型的运行时检查版本,但仅限于 .NET 4+

【讨论】:

【参考方案3】:

在 C# 7 中,我们可以使用元组来完成此操作:

public List<(int SomeVariable, string AnotherVariable)> TheMethod(SomeParameter)

  using (MyDC TheDC = new MyDC())
  
     var TheQueryFromDB = (....
                       select new  SomeVariable = ....,
                                    AnotherVariable = ....
                       ).ToList();

      return TheQueryFromDB
                .Select(s => (
                     SomeVariable = s.SomeVariable, 
                     AnotherVariable = s.AnotherVariable))
                 .ToList();
  

您可能需要安装System.ValueTuple nuget 包。

【讨论】:

这正是我要找的,听起来像 JS 类型 值得注意的是,如果您决定对命名元组进行 JSON 序列化,它将为您提供 Item1: xxx, Item2: xxx 等(如 - 名称丢失)【参考方案4】:

您不能返回匿名类型。你能创建一个可以返回的模型吗?否则,您必须使用object

Here is an article written by Jon Skeet on the subject

文章代码:

using System;

static class GrottyHacks

    internal static T Cast<T>(object target, T example)
    
        return (T) target;
    


class CheesecakeFactory

    static object CreateCheesecake()
    
        return new  Fruit="Strawberry", Topping="Chocolate" ;
    

    static void Main()
    
        object weaklyTyped = CreateCheesecake();
        var stronglyTyped = GrottyHacks.Cast(weaklyTyped,
            new  Fruit="", Topping="" );

        Console.WriteLine("Cheesecake: 0 (1)",
            stronglyTyped.Fruit, stronglyTyped.Topping);            
    

Or, here is another similar article

或者,正如其他人评论的那样,您可以使用dynamic

【讨论】:

我当然可以创建类型;我希望避免这样做。 第一个链接失效了,你不知道是不是转移到别处了吗?【参考方案5】:

当需要返回时,您可以使用 Tuple 类代替匿名类型:

注意:元组最多可以有 8 个参数。

return Tuple.Create(variable1, variable2);

或者,对于原始帖子中的示例:

public List<Tuple<SomeType, AnotherType>> TheMethod(SomeParameter)

  using (MyDC TheDC = new MyDC())
  
     var TheQueryFromDB = (....
                           select Tuple.Create(..., ...)
                           ).ToList();

      return TheQueryFromDB.ToList();
    

http://msdn.microsoft.com/en-us/library/system.tuple(v=vs.110).aspx

【讨论】:

元组可以有超过 8 个参数,因为第 8 个用于新元组。使用它是完全透明的。 (string mystring1, string mystring2, ...., string mystring42) myVar;【参考方案6】:

C# 编译器是一个两阶段编译器。在第一阶段,它只检查命名空间、类层次结构、方法签名等。方法主体仅在第二阶段编译。

在编译方法体之前,不会确定匿名类型。

所以编译器无法在第一阶段确定方法的返回类型。

这就是匿名类型不能用作返回类型的原因。

正如其他人建议的那样,如果您使用的是 .net 4.0 或更高版本,您可以使用Dynamic

如果我是你,我可能会创建一个类型并从方法中返回该类型。这样,未来的程序员就可以轻松维护您的代码并提高可读性。

【讨论】:

【参考方案7】:

三个选项:

选项1:

public class TheRepresentativeType 
    public ... SomeVariable get;set;
    public ... AnotherVariable get;set;


public IEnumerable<TheRepresentativeType> TheMethod(SomeParameter)

   using (MyDC TheDC = new MyDC())
   
     var TheQueryFromDB = (....
                           select new TheRepresentativeType SomeVariable = ....,
                                        AnotherVariable = ....
                           ).ToList();

     return TheQueryFromDB;
    

选项 2:

public IEnumerable TheMethod(SomeParameter)

   using (MyDC TheDC = new MyDC())
   
     var TheQueryFromDB = (....
                           select new TheRepresentativeType SomeVariable = ....,
                                        AnotherVariable = ....
                           ).ToList();
     return TheQueryFromDB;
    

您可以将其作为对象进行迭代

选项 3:

public IEnumerable<dynamic> TheMethod(SomeParameter)

   using (MyDC TheDC = new MyDC())
   
     var TheQueryFromDB = (....
                           select new TheRepresentativeType SomeVariable = ....,
                                        AnotherVariable = ....
                           ).ToList();

     return TheQueryFromDB; //You may need to call .Cast<dynamic>(), but I'm not sure
    

您将能够将其作为动态对象进行迭代并直接访问它们的属性

【讨论】:

非常感谢!选项 3 对我有用!我认为,这个答案被低估了。【参考方案8】:

使用 C# 7.0 我们仍然无法返回匿名类型,但我们支持 元组类型,因此我们可以返回 tuple (@987654323 @ 在这种情况下)。目前Tuple typesare not supported in expression trees,需要将数据加载到内存中。

您想要的最短代码版本可能如下所示:

public IEnumerable<(int SomeVariable, object AnotherVariable)> TheMethod()

    ...

    return (from data in TheDC.Data
        select new  data.SomeInt, data.SomeObject ).ToList()
        .Select(data => (SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject))

或者使用流畅的 Linq 语法:

return TheDC.Data
    .Select(data => new SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject)
    .ToList();
    .Select(data => (SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject))

使用 C# 7.1,我们可以省略元组的属性名称,它们将从元组初始化中推断出来,就像它适用于匿名类型一样:

select (data.SomeInt, data.SomeObject)
// or
Select(data => (data.SomeInt, data.SomeObject))

【讨论】:

【参考方案9】:

在这种情况下,您可以返回对象列表。

public List<object> TheMethod(SomeParameter)

  using (MyDC TheDC = new MyDC())
  
     var TheQueryFromDB = (....
                           select new  SomeVariable = ....,
                                        AnotherVariable = ....
                           ).ToList();

      return TheQueryFromDB ;
    

【讨论】:

【参考方案10】:
public List<SomeClass> TheMethod(SomeParameter)

  using (MyDC TheDC = new MyDC())
  
     var TheQueryFromDB = (....
                           select new SomeClass SomeVariable = ....,
                                        AnotherVariable = ....
                           ).ToList();

      return TheQueryFromDB.ToList();
    


public class SomeClass
   public string SomeVariableget;set
   public string AnotherVariableget;set;

创建自己的类并查询它是我所知道的最好的解决方案。据我所知,您不能在另一个方法中使用匿名类型返回值,因为它不会被识别。但是,它们可以被使用用同样的方法。 我曾经将它们返回为IQueryableIEnumerable,尽管它仍然无法让您看到匿名类型变量内部的内容。

我之前在尝试重构一些代码时遇到过类似的情况,您可以在这里查看:Refactoring and creating separate methods

【讨论】:

【参考方案11】:

只能使用动态关键字,

   dynamic obj = GetAnonymousType();

   Console.WriteLine(obj.Name);
   Console.WriteLine(obj.LastName);
   Console.WriteLine(obj.Age); 


   public static dynamic GetAnonymousType()
   
       return new  Name = "John", LastName = "Smith", Age=42;
   

但是使用动态类型关键字,您将失去编译时安全性、IDE IntelliSense 等...

【讨论】:

【参考方案12】:

有反射。

public object tst() 
    var a = new 
        prop1 = "test1",
        prop2 = "test2"
    ;

    return a;



public string tst2(object anonymousObject, string propName) 
    return anonymousObject.GetType().GetProperties()
        .Where(w => w.Name == propName)
        .Select(s => s.GetValue(anonymousObject))
        .FirstOrDefault().ToString();

示例:

object a = tst();
var val = tst2(a, "prop2");

输出:

test2

【讨论】:

【参考方案13】:

实际上可以从特定用例中的方法返回匿名类型。一起来看看吧!

使用 C# 7 可以从方法返回匿名类型,尽管它带有轻微的约束。我们将使用一种称为local function 的新语言功能以及间接技巧(另一层间接可以解决任何编程挑战,对吗?)。

这是我最近确定的一个用例。从AppSettings 加载所有配置值后,我想记录它们。为什么?因为有一些关于缺失值的逻辑可以恢复为默认值,一些解析等等。应用逻辑后记录值的一种简单方法是将它们全部放在一个类中并将其序列化到日志文件(使用 log4net)。我还想封装处理设置的复杂逻辑,并将其与我需要对它们做的任何事情分开。所有这些都无需创建仅用于一次性使用的命名类!

让我们看看如何使用创建匿名类型的本地函数来解决这个问题。

public static HttpClient CreateHttpClient()

    // I deal with configuration values in this slightly convoluted way.
    // The benefit is encapsulation of logic and we do not need to
    // create a class, as we can use an anonymous class.
    // The result resembles an expression statement that
    // returns a value (similar to expressions in F#)
    var config = Invoke(() =>
    
        // slightly complex logic with default value
        // in case of missing configuration value
        // (this is what I want to encapsulate)
        int? acquireTokenTimeoutSeconds = null;
        if (int.TryParse(ConfigurationManager.AppSettings["AcquireTokenTimeoutSeconds"], out int i))
        
            acquireTokenTimeoutSeconds = i;
        

        // more complex logic surrounding configuration values ...

        // construct the aggregate configuration class as an anonymous type!
        var c = new
        
            AcquireTokenTimeoutSeconds =
                acquireTokenTimeoutSeconds ?? DefaultAcquireTokenTimeoutSeconds,
            // ... more properties
        ;

        // log the whole object for monitoring purposes
        // (this is also a reason I want encapsulation)
        Log.InfoFormat("Config=0", c);
        return c;
    );

    // use this configuration in any way necessary...
    // the rest of the method concerns only the factory,
    // i.e. creating the HttpClient with whatever configuration
    // in my case this:
    return new HttpClient(...);

    // local function that enables the above expression
    T Invoke<T>(Func<T> func) => func.Invoke();

我已经成功地构建了一个匿名类,并且还封装了处理复杂设置管理的逻辑,全部在CreateHttpClient 和它自己的“表达式”中。这可能不是 OP 想要的,但它是一种具有匿名类型的轻量级方法,目前在现代 C# 中是可能的。

【讨论】:

【参考方案14】:

另一个选项可能是使用自动映射器:只要公共属性匹配,您将从匿名返回的对象转换为任何类型。 关键点是,返回对象,使用 linq 和 autommaper。 (或使用类似的想法返回序列化的json等或使用反射..)

using System.Linq;
using System.Reflection;
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

namespace UnitTestProject1

    [TestClass]
    public class UnitTest1
    
        [TestMethod]
        public void TestMethod1()
        
            var data = GetData();

            var firts = data.First();

            var info = firts.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).First(p => p.Name == "Name");
            var value = info.GetValue(firts);

            Assert.AreEqual(value, "One");
        


        [TestMethod]
        public void TestMethod2()
        
            var data = GetData();

            var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
            var mapper = config.CreateMapper();

            var users = data.Select(mapper.Map<User>).ToArray();

            var firts = users.First();

            Assert.AreEqual(firts.Name, "One");

        

        [TestMethod]
        public void TestMethod3()
        
            var data = GetJData();


            var users = JsonConvert.DeserializeObject<User[]>(data);

            var firts = users.First();

            Assert.AreEqual(firts.Name, "One");

        

        private object[] GetData()
        

            return new[]  new  Id = 1, Name = "One" , new  Id = 2, Name = "Two"  ;
        

        private string GetJData()
        

            return JsonConvert.SerializeObject(new [] new  Id = 1, Name = "One" , new  Id = 2, Name = "Two"  , Formatting.None);
        

        public class User
        
            public int Id  get; set; 
            public string Name  get; set; 
        
    


【讨论】:

【参考方案15】:

现在特别是使用本地函数,但您总是可以通过传递一个创建匿名类型的委托来做到这一点。

因此,如果您的目标是在相同的源上运行不同的逻辑,并能够将结果合并到一个列表中。不确定为了实现既定目标缺少什么细微差别,但只要您返回 T 并传递委托以生成 T,您就可以从函数返回匿名类型。

// returning an anonymous type
// look mom no casting
void LookMyChildReturnsAnAnonICanConsume()

    // if C# had first class functions you could do
    // var anonyFunc = (name:string,id:int) => new Name=name,Id=id;
    var items = new[]  new  Item1 = "hello", Item2 = 3  ;
    var itemsProjection =items.Select(x => SomeLogic(x.Item1, x.Item2, (y, i) => new  Word = y, Count = i ));
    // same projection = same type
    var otherSourceProjection = SomeOtherSource((y,i) => new Word=y,Count=i);
    var q =
        from anony1 in itemsProjection
        join anony2 in otherSourceProjection
            on anony1.Word equals anony2.Word
        select new anony1.Word,Source1Count=anony1.Count,Source2Count=anony2.Count;
    var togetherForever = itemsProjection.Concat(otherSourceProjection).ToList();


T SomeLogic<T>(string item1, int item2, Func<string,int,T> f)
    return f(item1,item2);

IEnumerable<T> SomeOtherSource<T>(Func<string,int,T> f)
    var dbValues = new []Tuple.Create("hello",1), Tuple.Create("bye",2);
    foreach(var x in dbValues)
        yield return f(x.Item1,x.Item2);

【讨论】:

关于评论“如果 C# 有一流的函数,你可以做到 var anonyFunc = (name:string,id:int) =&gt; new Name=name,Id=id;”:现在可以使用 C# 10 的 support for naturally typed lambdas【参考方案16】:

我不确定这个构造的名称,我认为这被称为匿名类型,但我可能错了。无论如何,我一直在寻找这个:

private (bool start, bool end) InInterval(int lower, int upper)

    return (5 < lower && lower < 10, 5 < upper && upper < 10);


private string Test()

    var response = InInterval(2, 6);
    if (!response.start && !response.end)
        return "The interval did not start nor end inside the target";
    else if (!response.start)
        return "The interval did not start inside the target";
    else if (!response.end)
        return "The interval did not end inside the target";
    else
        return "The interval is inside the target";

另一种调用方式可能是:

    var (start, end) = InInterval(2, 6);

【讨论】:

以上是关于在 C# 中返回匿名类型的主要内容,如果未能解决你的问题,请参考以下文章

C#控制台基础 返回类型为void的无参数委托与匿名方法

C#超级有用的一种类型—匿名类型

C#中的匿名类型

C#编程(十六)----------匿名类型

C# 匿名函数

C#进阶C# 匿名方法