转换为匿名类型

64

今天我遇到了以下问题,我想知道是否有解决方案。

我的想法是构建匿名类并将其作为WinForm BindingSource的数据源:

public void Init()
{
    var option1 = new
                  {
                      Id = TemplateAction.Update,
                      Option = "Update the Templates",
                      Description = "Bla bla 1."
                  };

    var option2 = new
                  {
                      Id = TemplateAction.Download,
                      Option = "Download the Templates",
                      Description = "Bla bla 2."
                  };

    var list = new[] {option1, option2}.ToList();

    bsOptions.DataSource = list; // my BindingSource

    // cboTemplates is a ComboBox
    cboTemplates.DataSource = bsOptions; 
    cboTemplates.ValueMember = "Id";
    cboTemplates.DisplayMember = "Option";

    lblInfoTemplates.DataBindings.Add("Text", bsOptions, "Description");
}

到目前为止,这很好用。

我遇到的问题是从BindingSource的“Current”属性中获取Id,因为我无法将其转换回匿名类型:

private void cmdOK_Click(object sender, EventArgs e)
{
    var option = (???)bsOptions.Current;
}

我想猜测找不到“Current”的类型并访问“Id”属性的方法吗?也许有人有好的解决方案...

我知道还有其他(更好的)方法来获取Id(反射,从ComboBox读取值,不使用匿名类型等),我只是好奇是否可能以一种简洁的方式从bsOptions.Current中获取类型。


匿名类有时可能很有用,但实际上,以这种方式使用,对我来说它是回归到VB时代 :/. - Clement Herreman
1
等到动态进入场景,我们只看到有关传递匿名对象的问题就应该庆幸了。 - Lasse V. Karlsen
1
没错,如果足够幸运的话,我们将在标签(Label)上拥有全新的“跑马灯”(marquee)属性 :D - Clement Herreman
1
访问C#匿名类型对象的副本。 - LukeH
7个回答

105
注意,根据评论,我要指出我也建议在需要像这样在程序中传递类型时使用真正的类型。匿名类型只应该在单个方法中本地使用(在我的观点中),但无论如何,以下是我的回答其余部分。
您可以使用技巧来欺骗编译器为您推断正确的类型:
using System;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            var a = new { Id = 1, Name = "Bob" };
            TestMethod(a);

            Console.Out.WriteLine("Press enter to exit...");
            Console.In.ReadLine();
        }

        private static void TestMethod(Object x)
        {
            // This is a dummy value, just to get 'a' to be of the right type
            var a = new { Id = 0, Name = "" };
            a = Cast(a, x);
            Console.Out.WriteLine(a.Id + ": " + a.Name);
        }

        private static T Cast<T>(T typeHolder, Object x)
        {
            // typeHolder above is just for compiler magic
            // to infer the type to cast x to
            return (T)x;
        }
    }
}

关键在于,在程序集内,相同的匿名类型(相同的属性,相同的顺序)解析为相同的类型,这使得上述技巧起作用。

private static T CastTo<T>(this Object value, T targetType)
{
    // targetType above is just for compiler magic
    // to infer the type to cast value to
    return (T)value;
}

使用方法:

var value = x.CastTo(a);

但是我们在这里真的在极限挑战。使用真正的类型,它看起来和感觉都更加清洁。


7
根据 Mads Torgersen 的说法,C#团队将这个技巧称为“按示例强制转换”。请参考他在此文章中的评论(第一个评论):http://tomasp.net/blog/cannot-return-anonymous-type-from-method.aspx - LukeH
1
这是一个非常聪明的技巧,但几乎所有“聪明”的代码一样,它是不应该被使用的代码。 - Brett Ryan
2
完全同意,在这种情况下,我至少会在框架中使用元组或类似的预定义类型,但我可能会为此情况创建一个新的类型。 - Lasse V. Karlsen
5
为什么每个人都说“哦,那很邪恶”,但是为什么?这只是在运行时可检查的,但许多语言都是以这种方式独占工作的,比如Python。这可能是一个可以接受的设计决策。无论如何,如果你从对象转换回任何东西,它都只能在运行时进行检查。那么,如果你将一个字符串或两个字符串转换回去,有什么区别呢? - Scott Stafford
2
我同意“不应该使用”,如果你真的太懒得编写一个小类,现在有 Tuple<,...>。 - Guillaume86
显示剩余5条评论

19

不要将对象强制转换为自定义类型,尝试使用动态类型。

您的事件处理程序可能如下所示:

private void cmdOK_Click(object sender, EventArgs e)
{
    dynamic option = bsOptions.Current;
    if (option.Id == 1) { doSomething(); }
      else { doSomethingElse(); }
}

3
+1 - 这是C# 4.0中的一个不错的选择。在这种情况下,option.Id将在运行时被评估。 - J. Andrew Laughlin

8

引用MSDN的话:

匿名类型无法转换为除了object以外的任何接口或类型。


6
在C# 3.0中,这是不可能的。你必须等到C# 4.0,它允许使用“dynamic”变量在运行时访问属性。

3
public class MyExtensMethods{

    public static T GetPropertyValue<T>(this Object obj, string property)
    {
        return (T)obj.GetType().GetProperty(property).GetValue(obj, null);
    }
}

class SomeClass
{
    public int ID{get;set;}
    public int FullName{get;set;}
}


// casts obj to type SomeClass
public SomeClass CastToSomeClass(object obj)
{
     return new SomeClass()
     {
         ID = obj.GetPropertyValue<int>("Id"),
         FullName = obj.GetPropertyValue<string>("LastName") + ", " + obj.GetPropertyValue<string>("FirstName")
     };
}

然后要进行投射,你需要执行以下操作:

var a = new { Id = 1, FirstName = "Bob", LastName="Nam" };
SomeClass myNewVar = CastToSomeClass(a);

1
@gsharp。我的使用情况是为了对Jsonresult进行单元测试,该结果发送了多个已实现类的匿名类型。所以我已经有了SomeClasses,但希望使用匿名类型来传递json。这对我非常完美,谢谢。 - justin arsine

2

1

你也可以使用该语法直接声明一个匿名类型的数组:

var data = new [] {
  new {Id = 0, Name = "Foo"},
  new {Id = 42, Name = "Bar"},
};

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