.NET会从“命名匿名”类型中受益吗?

时间:2009-03-17 02:19:12

标签: c# .net linq c#-4.0 anonymous-types

考虑一下:

var me = new { FirstName = "John", LastName = "Smith" };

这很好,因为我们可以这样做:

Console.WriteLine("{0} {1}", me.FirstName, me.LastName);

但是我们不能这样做:

public T GetMe()
{
    return new { FirstName = "John", LastName = "Smith" };
}

因为我们不知道T的类型。

我们可以这样做:

public object GetMe()
{
    return new { FirstName = "John", LastName = "Smith" };
}

但是我们必须使用反射来检查对象的属性才能访问它们:

var p = new Prog();
object o = p.GetMe();
Type t = o.GetType();
foreach (var prop in t.GetProperties())
{
    Console.WriteLine(prop.Name + ": " + prop.GetValue(o, null));
}

然而,如果我们可以在定义匿名类型时命名呢?当然它不再是匿名的,但它比普通的类定义更简洁和可维护。

考虑一下:

public Person GetMe()
{
    return new public class Person { FirstName = "John", LastName = "Smith" };
}

然后可以从方法返回复杂Linq查询的结果,而不必明确定义类。

考虑这个相对复杂的Linq查询:

List<int> list = new List<int>();
var query = from number in list
            select
                new
                    {
                        Number = number,
                        Square = number*number,
                        Absolute = Math.Abs(number),
                        Range = Enumerable.Range(0, number)
                    };

而不是像这样定义一个类:

public class MyNumbers
{
    public int Number { get; set; }
    public int Square { get; set; }
    public int Absolute { get; set; }
    public IEnumerable<int> Range { get; set; }
}

为了从一个方法返回查询变量,我们可以改为:

List<int> list = new List<int>();
return from number in list
            select new public class MyNumbers
                    {
                        Number = number,
                        Square = number*number,
                        Absolute = Math.Abs(number),
                        Range = Enumerable.Range(0, number)
                    };

7 个答案:

答案 0 :(得分:13)

实际上,你可以做一个“hack”来从一个方法中获取一个匿名类型。考虑一下:

public object MyMethod()
    {
        var myNewObject = new
        {
            stringProperty = "Hello, World!",
            intProperty = 1337,
            boolProperty = false
        };

        return myNewObject;
    }

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

您现在可以执行此操作:

var obj = MyMethod();
var myNewObj = Cast(obj, new { stringProperty = "", intProperty = 0, boolProperty = false });

myNewObj现在将是与匿名类型相同类型的对象。

答案 1 :(得分:9)

您需要的语言功能是:

public var GetMe()
{
    return new { FirstName = "John", LastName = "Smith" };
}

也就是说,var作为方法返回类型是有效的,编译器会根据返回的内容推断出实际类型。然后,您必须在呼叫站点执行此操作:

var me = GetMe();

任何两个具有相同类型成员的匿名类型都是相同的类型,因此如果您编写其他函数返回相同的模式,它们将具有相同的类型。对于任何类型A和B,其中B具有A的成员的子集,则A与B分配兼容(B类似于A的基类)。如果你写了:

public var GetMeFrom(var names)
{
    return new { FirstName = names["First"], LastName = names["Last"] };
}

编译器会有效地将此定义为具有两个类型参数的泛型方法,T1是名称的类型,T2T1上接受a的索引器返回的类型串。 T1会被约束,因此必须有一个接受字符串的索引器。在调用站点,您只需传递任何具有接受字符串的索引器并返回您喜欢的任何类型的内容,这将决定FirstNameLastName的类型在GetMeFrom返回的类型中。

因此类型推断会为您解决所有这些问题,自动捕获代码中可以发现的任何类型约束。

答案 2 :(得分:5)

恕我直言,根本问题与匿名类型无关,但声明一个类太冗长了。

选项1:

如果您可以声明这样的类:

public class MyClass
{ properties={ int Number, int Square, int Absolute, IEnumerable<int> Range } }

或其他一些类似的快速方式(如元组示例)然后你不会觉得需要使用匿名类型来执行hacky事情只是为了保存一些代码。

当'编译器即服务'到达C#5时,希望他们能够很好地集成它,我们将能够使用元编程来干净地解决这些问题。派对就像是1958年!

选项2:

或者,在C#4中,你可以只传递一个匿名类型dynamic并避免所有的转换。当然,如果重命名变量等,这会使您面临运行时错误。

选项3:

如果C#以与C ++相同的方式实现泛型,那么你可以将匿名类型传递给一个方法,只要它有正确的成员,就可以编译。您将获得静态类型安全的所有好处,并没有任何缺点。每次我必须在C#中键入where T : ISomething时,我都会因为没有这样做而感到恼火!

答案 3 :(得分:4)

您所描述的内容(命名为匿名类型)基本上是“元组类型”。

我认为它们是C#的一个很好的补充。

如果我正在为C#设计这样的功能,我会使用这样的语法公开它:

tuple<int x, int y>

这样你就可以:

public tuple<int x, int y> GetStuff()
{
}

然后我会更改匿名类型的定义,以便:

new { x = 2, y = 2}

的类型为tuple<int x, int y>,而不是匿名类型。

让它与当前的CLR一起使用有点棘手,因为一旦你可以在公共签名中命名匿名类型,你需要能够在单独编译的程序集中统一它们。它可以通过在使用元组类型的任何程序集中嵌入“模块构造函数”来实现。有关示例,请参阅this post

这种方法的唯一缺点是它不尊重CLR的类型生成的“懒惰”模型。这意味着使用许多不同元组类型的程序集可能会遇到稍慢的加载类型。更好的方法是直接向CLR添加对元组类型的支持。

但是,除了改变CLR之外,我认为模块构造函数方法是做这样的事情的最好方法。

答案 4 :(得分:1)

我很喜欢这个功能,有很多次我想要这个。

一个很好的例子是处理XML。您解析它们会返回一个对象,但是您需要将该对象的具体版本发送回调用者。很多时候,你得到的XML变化相当大,并且需要你创建许多类来处理它。如果您可以使用LinqToXml作为var构建对象,那么这只是返回它吗?

答案 5 :(得分:0)

我认为这对于元组来说是一个很好的编译魔术:

创建元组:

(int, string, Person) tuple = (8, "hello", new Person());

相当于:

Tuple<int,string,Person> tuple = new Tuple<int,string,Person>(8 ,"hello", new Person());

在一个功能中:

public (int, string, Person) GetTuple(){
    return ...
}

获取价值观:

int number = tuple[1];
string text = tuple[2];
Person person = tuple[3];

答案 6 :(得分:-1)

您是否可以使用FirstName和LastName属性创建一个接口并使用它?