【问题标题】:Would .NET benefit from "named anonymous" types?.NET 会从“命名匿名”类型中受益吗?
【发布时间】:2009-03-17 02:19:12
【问题描述】:

考虑一下:

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

【问题讨论】:

  • 当您开始在其他情况下使用 MyNumbers 时,这会变得非常难看。如果您使用跨方法边界的类型,则如果该类型未按照您的建议内联声明,则该类型更易于维护和阅读。
  • 对spoon16的评论+1。
  • 公平点虽然我认为你可以在编译器中加入一些智能来缓解这些问题。
  • 我认为让它不丑的关键是使用单独的语法来命名类型,而不是使用创建值的语法。示例见我的帖子。
  • 老实说,我在阅读标题行时差点把自己拉黑。仔细想想也看不出有多大的好处。

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


【解决方案1】:

实际上,您可以通过“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 现在将成为与匿名类型相同类型的对象。

【讨论】:

  • 我以前没见过这个。这是一个非常酷的技巧......虽然我不认为使用它通常是一个好主意
  • 不错的技巧虽然匿名类型仍然是内部的,我相信因此不能跨程序集使用。
  • 是的,这只是一个 hack,我看不到自己在生产代码中使用它。不过,您应该问自己 Jonathan,如果您要从另一个程序集引用它,您真的认为匿名类型是您想要的吗??
  • @BFree 他们不是匿名的,因此是“”。我想更好的名字是自动类型或内联类型。
  • 我想为纯粹的光彩投票 +1,为纯粹的丑陋投票 -1。
【解决方案2】:

您需要的语言功能是:

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

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

var me = GetMe();

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

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

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

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

【讨论】:

  • 没错,我一直想要这个!我认为 D 有这个方法返回类型推断。
【解决方案3】:

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

选项 1:

如果你可以这样声明一个类:

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

或其他类似的快速方法(如元组示例),那么您就不会觉得有必要为了节省一些代码而对匿名类型做一些骇人听闻的事情。

当“编译器即服务”出现在 C#5 中时,希望他们能很好地集成它,我们将能够使用元编程干净利落地解决这类问题。派对就像 1958 年一样!

选项 2:

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

选项 3:

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

【讨论】:

  • 很多时候用胶带将一堆变量绑定在一起很有用。我的偏好是为此目的使用公共字段值类型,如果需要可变或不可变的引用语义,则使用简单的通用 ExposedFieldHolder&lt;T&gt;ImmutableHolder&lt;T&gt;。在某些方面,我希望 .NET 包含两种值类型——一种被设计为表现得像对象,另一种被设计为用胶带将变量绑定在一起。如果可以声明像 StructTuple&lt;T1,T2&gt;{public T1 v1; public T2 v2;} 这样的类型,以便在装箱时真正不可变......
  • ...那么StructTuple&lt;Toyota,Dog&gt; 可以被视为StructTuple&lt;Car,Animal&gt; 的子类型。事实上,所有装箱的结构总是可变的这一事实使得结构类型协方差即使在原本有意义的情况下也是不可能的。
【解决方案4】:

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

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

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

tuple<int x, int y>

这样你就可以做到:

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

然后我会更改匿名类型的定义,这样:

new { x = 2, y = 2}

tuple&lt;int x, int y&gt; 作为它的类型,而不是匿名类型。

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

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

但是,除了更改 CLR 之外,我认为模块构造方法是执行此类操作的最佳方式。

【讨论】:

  • .Net 4 正在添加对元组的 BCL 支持。例如,weblogs.asp.net/podwysocki/archive/2008/11/16/…
  • BCL 中的元组类型不像匿名类型那样将字段名称视为类型标识的一部分,因此并不完全相同。
  • 我认为不支持具有命名字段的元组是一件坏事。您多久在您的领域中使用“item1”和“item2”之类的名称进行编程?我猜可能永远不会
  • 使用我的解决方案,您可以随意命名您的字段。我更喜欢它而不是 First、Second、Third 元组字段。
  • 你可以用我写的东西做同样的事情。只是 tuple 比整个类定义短得多。
【解决方案5】:

我会喜欢这个功能,我已经有很多次想要这个了。

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

【讨论】:

    【解决方案6】:

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

    创建一个元组:

    (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];
    

    【讨论】:

    • 有趣的建议。将它作为参数传递不会那么干净 public void Foo((int, string, Person) tuple) {}
    • 为什么是 lhs?只是var tuple = (8, "hello", new Person())?会更好。
    • 旧帖子,但这比实际存在的 var tuple = Tuple.Create(8, "hello", new Person()); ?
    【解决方案7】:

    您能否创建一个具有 FirstName 和 LastName 属性的接口并使用它?

    【讨论】:

      猜你喜欢
      • 2010-09-17
      • 2011-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多