【问题标题】:C# anonymous object with properties from dictionary具有字典属性的 C# 匿名对象
【发布时间】:2015-06-07 11:11:52
【问题描述】:

我正在尝试将字典转换为匿名类型,每个键都有一个属性。

我尝试用谷歌搜索,但我只能找到如何将匿名对象转换为字典。

我的字典看起来像这样:

var dict = new Dictionary<string, string>
{
    {"Id", "1"},
    {"Title", "My title"},
    {"Description", "Blah blah blah"},
};

我想返回一个看起来像这样的匿名对象。

var o = new 
{
    Id = "1",
    Title = "My title",
    Description = "Blah blah blah"
};

所以我希望它遍历字典中的每个 keyValuePair 并在对象中为每个键创建一个属性。

我不知道从哪里开始。

请帮忙。

【问题讨论】:

  • 你用匿名对象做什么? C# 不是 javascript。
  • 我不确定这种方式是否可行,因为您在此处提供的对象是在编译时生成到类中的,因此您无法在运行时创建属性。跨度>
  • 匿名类型不是动态类型:看看这个问题:stackoverflow.com/questions/391529/…
  • 即使有可能,调用者也无法使用该对象,因为该对象的成员将由运行时确定。 C# 是静态录制的。

标签: c# object dictionary


【解决方案1】:

你不能,基本上。匿名类型由编译器创建,因此它们存在于您的程序集中,其中包含所有属性名称。 (在这种情况下,属性 types 不是问题 - 作为实现细节,编译器会创建一个泛型类型,然后使用适当的类型参数创建该类型的实例。)

您要求的类型具有在 执行 时确定的属性 - 这与匿名类型的工作方式不符。您基本上必须在执行时使用它来编译代码 - 这会很痛苦,因为它会在不同的程序集中,并且匿名类型是内部的......

也许您应该改用ExpandoObject?然后任何使用dynamic 的东西都可以正常访问属性。

【讨论】:

  • 他仍然可以通过反射动态创建一个新类型。我不它会有用或有智能......这似乎是一个XY问题
  • @xanatos 虽然他不能动态创建一个新的匿名类型。这必须由编译器在编译时生成。是的,闻起来像 XY 问题。
  • @SriramSakthivel “匿名”的概念简直就是“你不用 C# 代码编写它,编译器用一个有趣的名称和一些属性生成它”......他肯定可以创建一个新类型,给它起一个有趣的名字和一些属性......从技术上讲,它不会是匿名类型,但差别很小
  • @xanatos 并不是我不同意。你确实是对的。我的观点是用 C# 术语匿名类型是编译器创建的,而不是我们。
  • @SriramSakthivel 所以他应该使用CodeDomProvider/CSharpCodeProvider,所以即使是表格也是正确的:)
【解决方案2】:

以下是作为ExpandoObject 的方法(来自this post 的帮助)

var dict = new Dictionary<string, string>
{
    {"Id", "1"},
    {"Title", "My title"},
    {"Description", "Blah blah blah"},
};

var expando = new ExpandoObject()  as IDictionary<string, Object>;
foreach(var kvp in dict)
    expando.Add(kvp.Key, kvp.Value);

【讨论】:

    【解决方案3】:

    基本上,这不是您可以轻易做到的事情;匿名类型仍然拥有所有常见的幕后代码:只是你看不到它。您可以通过dynamic 做同样的事情,但这不适用于期望反射等的API - 因此,对于大多数代码字典,您必须按键访问访问会比匿名类型或dynamic 类型更方便。

    【讨论】:

      【解决方案4】:

      它不是匿名对象,但您可以使用DynamicObject 来完成此操作!您可以执行以下操作。

      public class MyExpando : DynamicObject
      {
          Dictionary<string, object> dictionary;
          public MyExpando()
          {
             dictionary = new Dictionary<string, object>
             {
                  {"Id", "1"},
                  {"Title", "My title"},
                  {"Description", "Blah blah blah"},
             };
          }
      
          public override bool TryGetMember(GetMemberBinder binder, out object result)
          {
              return dictionary.TryGetValue(binder.Name, out result);
          }
      
          public override bool TrySetMember(SetMemberBinder binder, object value)
          {
              if (!dictionary.ContainsKey(binder.Name))
                  return false;
              dictionary[binder.Name] = value;
              return true;
          }
      }
      

      然后你的对象就有你想要的属性了。

      dynamic o = new MyExpando();
      Console.WriteLine("Id :  " + o.Id + ", Title : " + o.Title + ", Description : " + o.Description);
      

      当然,缺点是在编译时没有类型检查,也没有在编译时检查成员是否存在。

      【讨论】:

        【解决方案5】:

        现在,每月至少有一次有人询问如何在运行时创建匿名类型...这是回复:

        using System;
        using System.Collections.Concurrent;
        using System.Collections.Generic;
        using System.Diagnostics;
        using System.Linq;
        using System.Reflection;
        using System.Reflection.Emit;
        using System.Runtime.CompilerServices;
        using System.Text;
        using System.Threading;
        
        /// <summary>
        /// The code generated should be nearly equal to the one generated by
        /// csc 12.0.31101.0 when compiling with /optimize+ /debug-. The main
        /// difference is in the GetHashCode() (the base init_hash used is 
        /// compiler-dependant) and in the maxstack of the generated methods.
        /// Note that Roslyn (at least the one present at 
        /// tryroslyn.azurewebsites.net) generates different code for anonymous
        /// types.
        /// </summary>
        public static class AnonymousType
        {
            private static readonly ConcurrentDictionary<string, Type> GeneratedTypes = new ConcurrentDictionary<string, Type>();
        
            private static readonly AssemblyBuilder AssemblyBuilder;
            private static readonly ModuleBuilder ModuleBuilder;
            private static readonly string FileName;
        
            // Some objects we cache
            private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
            private static readonly CustomAttributeBuilder DebuggerBrowsableAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerBrowsableAttribute).GetConstructor(new[] { typeof(DebuggerBrowsableState) }), new object[] { DebuggerBrowsableState.Never });
            private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerHiddenAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
        
            private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes);
            private static readonly MethodInfo ObjectToString = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
        
            private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes);
            private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod("Append", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
            private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod("Append", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object) }, null);
        
            private static readonly Type EqualityComparer = typeof(EqualityComparer<>);
            private static readonly Type EqualityComparerGenericArgument = EqualityComparer.GetGenericArguments()[0];
            private static readonly MethodInfo EqualityComparerDefault = EqualityComparer.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null);
            private static readonly MethodInfo EqualityComparerEquals = EqualityComparer.GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public, null, new[] { EqualityComparerGenericArgument, EqualityComparerGenericArgument }, null);
            private static readonly MethodInfo EqualityComparerGetHashCode = EqualityComparer.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public, null, new[] { EqualityComparerGenericArgument }, null);
        
            private static int Index = -1;
        
            static AnonymousType()
            {
                var assemblyName = new AssemblyName("AnonymousTypes");
        
                FileName = assemblyName.Name + ".dll";
        
                AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
                ModuleBuilder = AssemblyBuilder.DefineDynamicModule("AnonymousTypes", FileName);
            }
        
            public static void Dump()
            {
                AssemblyBuilder.Save(FileName);
            }
        
            /// <summary>
            /// 
            /// </summary>
            /// <param name="types"></param>
            /// <param name="names"></param>
            /// <returns></returns>
            public static Type CreateType(Type[] types, string[] names)
            {
                if (types == null)
                {
                    throw new ArgumentNullException("types");
                }
        
                if (names == null)
                {
                    throw new ArgumentNullException("names");
                }
        
                if (types.Length != names.Length)
                {
                    throw new ArgumentException("names");
                }
        
                // Anonymous classes are generics based. The generic classes
                // are distinguished by number of parameters and name of 
                // parameters. The specific types of the parameters are the 
                // generic arguments. We recreate this by creating a fullName
                // composed of all the property names, separated by a "|"
                string fullName = string.Join("|", names.Select(x => Escape(x)));
        
                Type type;
        
                if (!GeneratedTypes.TryGetValue(fullName, out type))
                {
                    // We create only a single class at a time, through this lock
                    // Note that this is a variant of the double-checked locking.
                    // It is safe because we are using a thread safe class.
                    lock (GeneratedTypes)
                    {
                        if (!GeneratedTypes.TryGetValue(fullName, out type))
                        {
                            int index = Interlocked.Increment(ref Index);
        
                            string name = names.Length != 0 ? string.Format("<>f__AnonymousType{0}`{1}", index, names.Length) : string.Format("<>f__AnonymousType{0}", index);
                            TypeBuilder tb = ModuleBuilder.DefineType(name, TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
                            tb.SetCustomAttribute(CompilerGeneratedAttributeBuilder);
        
                            GenericTypeParameterBuilder[] generics = null;
        
                            if (names.Length != 0)
                            {
                                string[] genericNames = Array.ConvertAll(names, x => string.Format("<{0}>j__TPar", x));
                                generics = tb.DefineGenericParameters(genericNames);
                            }
                            else
                            {
                                generics = new GenericTypeParameterBuilder[0];
                            }
        
                            // .ctor
                            ConstructorBuilder constructor = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, generics);
                            constructor.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
                            ILGenerator ilgeneratorConstructor = constructor.GetILGenerator();
                            ilgeneratorConstructor.Emit(OpCodes.Ldarg_0);
                            ilgeneratorConstructor.Emit(OpCodes.Call, ObjectCtor);
        
                            var fields = new FieldBuilder[names.Length];
        
                            // There are two for cycles because we want to have
                            // all the getter methods before all the other 
                            // methods
                            for (int i = 0; i < names.Length; i++)
                            {
                                // field
                                fields[i] = tb.DefineField(string.Format("<{0}>i__Field", names[i]), generics[i], FieldAttributes.Private | FieldAttributes.InitOnly);
                                fields[i].SetCustomAttribute(DebuggerBrowsableAttributeBuilder);
        
                                // .ctor
                                constructor.DefineParameter(i + 1, ParameterAttributes.None, names[i]);
                                ilgeneratorConstructor.Emit(OpCodes.Ldarg_0);
        
                                if (i == 0)
                                {
                                    ilgeneratorConstructor.Emit(OpCodes.Ldarg_1);
                                }
                                else if (i == 1)
                                {
                                    ilgeneratorConstructor.Emit(OpCodes.Ldarg_2);
                                }
                                else if (i == 2)
                                {
                                    ilgeneratorConstructor.Emit(OpCodes.Ldarg_3);
                                }
                                else if (i < 255)
                                {
                                    ilgeneratorConstructor.Emit(OpCodes.Ldarg_S, (byte)(i + 1));
                                }
                                else
                                {
                                    // Ldarg uses a ushort, but the Emit only
                                    // accepts short, so we use a unchecked(...),
                                    // cast to short and let the CLR interpret it
                                    // as ushort
                                    ilgeneratorConstructor.Emit(OpCodes.Ldarg, unchecked((short)(i + 1)));
                                }
        
                                ilgeneratorConstructor.Emit(OpCodes.Stfld, fields[i]);
        
                                // getter
                                MethodBuilder getter = tb.DefineMethod(string.Format("get_{0}", names[i]), MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, generics[i], Type.EmptyTypes);
                                ILGenerator ilgeneratorGetter = getter.GetILGenerator();
                                ilgeneratorGetter.Emit(OpCodes.Ldarg_0);
                                ilgeneratorGetter.Emit(OpCodes.Ldfld, fields[i]);
                                ilgeneratorGetter.Emit(OpCodes.Ret);
        
                                PropertyBuilder property = tb.DefineProperty(names[i], PropertyAttributes.None, CallingConventions.HasThis, generics[i], Type.EmptyTypes);
                                property.SetGetMethod(getter);
                            }
        
                            // ToString()
                            MethodBuilder toString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(string), Type.EmptyTypes);
                            toString.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
                            ILGenerator ilgeneratorToString = toString.GetILGenerator();
        
                            ilgeneratorToString.DeclareLocal(typeof(StringBuilder));
        
                            ilgeneratorToString.Emit(OpCodes.Newobj, StringBuilderCtor);
                            ilgeneratorToString.Emit(OpCodes.Stloc_0);
        
                            // Equals
                            MethodBuilder equals = tb.DefineMethod("Equals", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(bool), new[] { typeof(object) });
                            equals.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
                            equals.DefineParameter(1, ParameterAttributes.None, "value");
                            ILGenerator ilgeneratorEquals = equals.GetILGenerator();
                            ilgeneratorEquals.DeclareLocal(tb);
        
                            ilgeneratorEquals.Emit(OpCodes.Ldarg_1);
                            ilgeneratorEquals.Emit(OpCodes.Isinst, tb);
                            ilgeneratorEquals.Emit(OpCodes.Stloc_0);
                            ilgeneratorEquals.Emit(OpCodes.Ldloc_0);
        
                            Label equalsLabel = ilgeneratorEquals.DefineLabel();
        
                            // GetHashCode()
                            MethodBuilder getHashCode = tb.DefineMethod("GetHashCode", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(int), Type.EmptyTypes);
                            getHashCode.SetCustomAttribute(DebuggerHiddenAttributeBuilder);
                            ILGenerator ilgeneratorGetHashCode = getHashCode.GetILGenerator();
                            ilgeneratorGetHashCode.DeclareLocal(typeof(int));
        
                            if (names.Length == 0)
                            {
                                ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4_0);
                            }
                            else
                            {
                                // As done by Roslyn
                                // Note that initHash can vary, because
                                // string.GetHashCode() isn't "stable" for 
                                // different compilation of the code
                                int initHash = 0;
        
                                for (int i = 0; i < names.Length; i++)
                                {
                                    initHash = unchecked(initHash * (-1521134295) + fields[i].Name.GetHashCode());
                                }
        
                                // Note that the CSC seems to generate a 
                                // different seed for every anonymous class
                                ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, initHash);
                            }
        
                            for (int i = 0; i < names.Length; i++)
                            {
                                // Equals()
                                Type equalityComparerT = EqualityComparer.MakeGenericType(generics[i]);
                                MethodInfo equalityComparerTDefault = TypeBuilder.GetMethod(equalityComparerT, EqualityComparerDefault);
                                MethodInfo equalityComparerTEquals = TypeBuilder.GetMethod(equalityComparerT, EqualityComparerEquals);
        
                                ilgeneratorEquals.Emit(OpCodes.Brfalse_S, equalsLabel);
                                ilgeneratorEquals.Emit(OpCodes.Call, equalityComparerTDefault);
                                ilgeneratorEquals.Emit(OpCodes.Ldarg_0);
                                ilgeneratorEquals.Emit(OpCodes.Ldfld, fields[i]);
                                ilgeneratorEquals.Emit(OpCodes.Ldloc_0);
                                ilgeneratorEquals.Emit(OpCodes.Ldfld, fields[i]);
                                ilgeneratorEquals.Emit(OpCodes.Callvirt, equalityComparerTEquals);
        
                                // GetHashCode();
                                MethodInfo EqualityComparerTGetHashCode = TypeBuilder.GetMethod(equalityComparerT, EqualityComparerGetHashCode);
        
                                ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0);
                                ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, -1521134295);
                                ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0);
                                ilgeneratorGetHashCode.Emit(OpCodes.Mul);
                                ilgeneratorGetHashCode.Emit(OpCodes.Call, equalityComparerTDefault);
                                ilgeneratorGetHashCode.Emit(OpCodes.Ldarg_0);
                                ilgeneratorGetHashCode.Emit(OpCodes.Ldfld, fields[i]);
                                ilgeneratorGetHashCode.Emit(OpCodes.Callvirt, EqualityComparerTGetHashCode);
                                ilgeneratorGetHashCode.Emit(OpCodes.Add);
        
                                // ToString()
                                ilgeneratorToString.Emit(OpCodes.Ldloc_0);
                                ilgeneratorToString.Emit(OpCodes.Ldstr, i == 0 ? string.Format("{{ {0} = ", names[i]) : string.Format(", {0} = ", names[i]));
                                ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString);
                                ilgeneratorToString.Emit(OpCodes.Pop);
                                ilgeneratorToString.Emit(OpCodes.Ldloc_0);
                                ilgeneratorToString.Emit(OpCodes.Ldarg_0);
                                ilgeneratorToString.Emit(OpCodes.Ldfld, fields[i]);
                                ilgeneratorToString.Emit(OpCodes.Box, generics[i]);
                                ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendObject);
                                ilgeneratorToString.Emit(OpCodes.Pop);
                            }
        
                            // .ctor
                            ilgeneratorConstructor.Emit(OpCodes.Ret);
        
                            // Equals()
                            if (names.Length == 0)
                            {
                                ilgeneratorEquals.Emit(OpCodes.Ldnull);
                                ilgeneratorEquals.Emit(OpCodes.Ceq);
                                ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0);
                                ilgeneratorEquals.Emit(OpCodes.Ceq);
                            }
                            else
                            {
                                ilgeneratorEquals.Emit(OpCodes.Ret);
                                ilgeneratorEquals.MarkLabel(equalsLabel);
                                ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0);
                            }
        
                            ilgeneratorEquals.Emit(OpCodes.Ret);
        
                            // GetHashCode()
                            ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0);
                            ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0);
                            ilgeneratorGetHashCode.Emit(OpCodes.Ret);
        
                            // ToString()
                            ilgeneratorToString.Emit(OpCodes.Ldloc_0);
                            ilgeneratorToString.Emit(OpCodes.Ldstr, names.Length == 0 ? "{ }" : " }");
                            ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString);
                            ilgeneratorToString.Emit(OpCodes.Pop);
                            ilgeneratorToString.Emit(OpCodes.Ldloc_0);
                            ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString);
                            ilgeneratorToString.Emit(OpCodes.Ret);
        
                            type = tb.CreateType();
        
                            type = GeneratedTypes.GetOrAdd(fullName, type);
                        }
                    }
                }
        
                if (types.Length != 0)
                {
                    type = type.MakeGenericType(types);
                }
        
                return type;
            }
        
            private static string Escape(string str)
            {
                // We escape the \ with \\, so that we can safely escape the
                // "|" (that we use as a separator) with "\|"
                str = str.Replace(@"\", @"\\");
                str = str.Replace(@"|", @"\|");
                return str;
            }
        }
        

        这个又大又胖的代码块将在运行时使用Reflection.Emit 生成一些具有任意命名属性的类(您甚至可以使用C# 中非法的字符)。这些类将遵循 C# 编译器 12.0.31101.0 生成的匿名类的命名约定。即使在 IL 级别,它们也应该几乎相等。所有各种Equals()GetHashCode()ToString() 都是由C# 编译器12.0.31101.0 实现的,所有字段都具有相同的名称,插入了CompilerGeneratedAttribute(这在道德上是错误的,我知道:- ) ) 等等。所以这些类生成了walk like an anonymous duck 并且像一只匿名鸭子一样嘎嘎......它们实际上是匿名鸭子:-)(请注意,罗斯林鸭子属于不同的亚种......它们的羽毛有点不同......一只匿名鸭子鉴赏家可以轻松区分这两个亚种:-))

        现在,如果您想问cui prodest? 生成的类只能通过反射使用,因此很难使用,但是它们有一个特定的用例,即 Marc Gravell 个性化:. NET 框架,大量使用反射来呈现对象(例如所有各种数据网格)。这些部分与dynamic 对象不兼容,并且通常不支持object[]。解决方案通常是将数据封装在 DataTable... 中,或者您可以使用它:-)

        在您的具体情况下,您可以使用此方法:

        public static object FromDictToAnonymousObj<TValue>(IDictionary<string, TValue> dict)
        {
            var types = new Type[dict.Count];
        
            for (int i = 0; i < types.Length; i++)
            {
                types[i] = typeof(TValue);
            }
        
            // dictionaries don't have an order, so we force an order based
            // on the Key
            var ordered = dict.OrderBy(x => x.Key).ToArray();
        
            string[] names = Array.ConvertAll(ordered, x => x.Key);
        
            Type type = AnonymousType.CreateType(types, names);
        
            object[] values = Array.ConvertAll(ordered, x => (object)x.Value);
        
            object obj = type.GetConstructor(types).Invoke(values);
        
            return obj;
        }
        

        像这样:

        var dict = new Dictionary<string, string>
        {
            {"Id", "1"},
            {"Title", "My title"},
            {"Description", "Blah blah blah"},
        };
        
        object obj1 = FromDictToAnonymousObj(dict);
        

        获取你的对象。

        【讨论】:

        • 很好,但我做了一个更有限的版本,但我希望它像编译器那样合并匹配类型。
        • @NetMage 这是一个问题......这不是很困难(您只需要以某种方式提取您的类型的“签名”并将其用作字典中的键) ,但仍然需要多行代码
        猜你喜欢
        • 2014-10-15
        • 2022-01-23
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 2020-06-18
        • 2018-07-04
        • 1970-01-01
        • 2019-05-28
        相关资源
        最近更新 更多