在C#中返回匿名类型


100

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

public "TheAnonymousType" TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new { SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return "TheAnonymousType";
    }
}

5
您为什么要返回匿名类型?您如何在其他任何地方使用该结果?
Yuck


5
@Yuck,如果您返回的是json或C#类型无关紧要的东西
2014年

10
我认为这个问题不是没有道理的。我实际上已经需要做几次。当使用实体框架时,它更明显,您想在一个函数中进行查询,并在多个位置使用结果。当在屏幕上显示结果,然后在报表中使用相同的结果或导出到excel时,我经常需要这样做。查询可能包含很多过滤器,例如来自UI的过滤器。您真的不想在多个地方创建相同的查询,或者当您想要添加到结果中时很容易不同步
Kevbo 2015年

Answers:


94

你不能

你只能返回object,或物体的容器,例如IEnumerable<object>IList<object>等等。


51
或者dynamic。这使得它的使用变得如此容易。
vcsjones 2012年

嗯,那么您只能在方法中使用匿名类型,而不能用作返回值?
frenchie

2
@frenchie:是的,仅在成员体内。如果要返回它-使其成为众所周知的类型。
abatishchev 2012年

11
使用动态方法不是解决方案,匿名类型的字段不是公共的,而是内部的。
汉斯·帕桑

7
@HansPassant假设调用者在同一程序集中,则它仍然(有点)有用。就其价值而言,字段是公共的-类型是内部的。我通常在营地中,无论如何您都不应该返回匿名类型。
vcsjones 2012年

42

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


30

在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.ValueTuplenuget软件包。


27

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

这是乔恩·斯凯特(Jon Skeet)撰写的有关该主题的文章

文章中的代码:

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

或者,这是另一篇类似的文章

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


8
我当然可以创建一个类型。我一直想避免这样做。
frenchie

第一个链接已死,您难道不知道它是否已转移到其他地方吗?
雷米

17

当需要返回时,可以使用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/zh-CN/library/system.tuple(v=vs.110).aspx


10

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

直到方法主体被编译后,匿名类型才能确定。

因此,编译器无法在第一阶段确定方法的返回类型。

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

正如其他人建议的那样,如果您使用的是.net 4.0或磨碎机,则可以使用Dynamic

如果我是你,我可能会创建一个类型并从方法中返回该类型。这样,对于将来的程序员来说,维护您的代码并使其更具可读性很容易。


8

三种选择:

选项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

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

public List<object> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new { SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return TheQueryFromDB ;
    }
}

3

使用C#7.0,我们仍然无法返回匿名类型,但是我们拥有元组类型的支持,因此我们可以返回tupleSystem.ValueTuple<T1,T2>在这种情况下)的集合。Tuple types 表达式树当前不支持,您需要将数据加载到内存中。

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

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

2
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 SomeVariable{get;set}
   public string AnotherVariable{get;set;}
}

创建自己的类并对其进行查询是我所知的最佳解决方案。据我所知,您不能在另一种方法中使用匿名类型返回值,因为它不会被识别,但是可以在同一方法中使用它们方法。我曾经以IQueryableor或返回它们IEnumerable,尽管它仍然不能让您看到匿名类型变量的内容。

在尝试重构某些代码之前,我遇到了类似的问题,您可以在此处进行检查:重构和创建单独的方法


2

用反射。

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

1

您只能使用动态关键字,

   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等...


0

另一种选择是使用自动映射器:只要公共属性匹配,您将可以从匿名返回的对象转换为任何类型。关键点是,返回对象使用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; }
        }
    }

}

0

现在特别具有局部功能,但是您始终可以通过传递使该匿名类型成为代表的委托来实现。

因此,如果您的目标是在相同的源上运行不同的逻辑,并能够将结果合并到一个列表中。不知道要达到既定目标缺少什么细微差别,但是只要返回a T并传递一个make 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);
}

0

在特定用例中,实际上有可能从方法中返回匿名类型。我们来看一下!

使用C#7,可以从方法中返回匿名类型,尽管它带有一些约束。我们将使用一种称为本地函数的新语言功能以及一个间接技巧(间接的另一层可以解决任何编程难题,对吗?)。

这是我最近确定的一个用例。从中加载所有配置值后,我想记录所有配置值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#中是可能的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.