简体   繁体   English

如何将对象序列化为 C# 对象初始值设定项代码?

[英]How can I serialize an object to C# object initializer code?

I'm looking to take an in-memory object (or JSON serialization of an object) and emit C# code to produce an equivalent object.我正在寻找一个内存对象(或对象的 JSON 序列化)并发出 C# 代码来生成等效的对象。

This would be useful for pulling known-good examples from a repository to use as starting points in unit tests.这对于从存储库中提取已知良好的示例以用作单元测试的起点非常有用。 We have considered deserializing JSON, but C# code would have an edge when it comes to refactoring.我们考虑过反序列化 JSON,但 C# 代码在重构方面具有优势。

There is an interesting Visual Studio extension that addresses this;有一个有趣的 Visual Studio 扩展可以解决这个问题; the Object Exporter . 对象导出器 It allows serialization of an in-memory object into C# Object initialization code, JSON and XML.它允许将内存中的对象序列化为 C# 对象初始化代码、JSON 和 XML。 I haven't tried it yet but looks intriguing;我还没有尝试过,但看起来很有趣; will update after trying it out.试用后会更新。

If your model is simple, you could use reflection and a string builder to output C# directly.如果您的模型很简单,您可以使用反射和字符串生成器直接输出 C#。 I've done this to populate unit test data exactly as you discussed.我这样做是为了完全按照您的讨论填充单元测试数据。

The code sample below was written in a few minutes and generated an object initializer that needed some hand tweaking.下面的代码示例是在几分钟内编写的,并生成了一个需要手动调整的对象初始值设定项。 A more robust / less buggy function could be written if you plan on doing this a lot.如果您计划经常这样做,可以编写一个更健壮/错误更少的函数。

The second function is recursive, iterating over any Lists within the object, and generating code for those as well.第二个函数是递归的,迭代对象内的任何列表,并为这些列表生成代码。

Disclaimer: This worked for my simple model with basic data types.免责声明:这适用于我的具有基本数据类型的简单模型。 It generated code that needed cleanup but allowed me to move on quickly.它生成了需要清理的代码,但允许我快速前进。 It is only here to serve as an example of how this could be done.这里只是作为一个例子来说明如何做到这一点。 Hopefully, it inspires someone to write their own.希望它激励某人写自己的东西。

In my case, I had an instance of this large dataset (results) that was loaded from the database.就我而言,我有一个从数据库加载的大型数据集(结果)的实例。 In order to remove the database dependency from my unit test, I handed the object to this function which spits out the code that allowed me to mock the object in my test class.为了从我的单元测试中删除数据库依赖项,我将对象交给了这个函数,该函数吐出允许我在我的测试类中模拟对象的代码。

    private void WriteInstanciationCodeFromObject(IList results)
    {

        //declare the object that will eventually house C# initialization code for this class
        var testMockObject = new System.Text.StringBuilder();

        //start building code for this object
        ConstructAndFillProperties(testMockObject, results);

        var codeOutput = testMockObject.ToString();
    }


    private void ConstructAndFillProperties(StringBuilder testMockObject, IList results)
    {

        testMockObject.AppendLine("var testMock = new " + results.GetType().ToString() + "();");

        foreach (object obj in results)
        {

            //if this object is a list, write code for its contents

            if (obj.GetType().GetInterfaces().Contains(typeof(IList)))
            {
                ConstructAndFillProperties(testMockObject, (IList)obj);
            }

            testMockObject.AppendLine("testMock.Add(new " + obj.GetType().Name + "() {");

            foreach (var property in obj.GetType().GetProperties())
            {

               //if this property is a list, write code for its contents
                if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
                {
                    ConstructAndFillProperties(testMockObject, (IList)property.GetValue(obj, null));
                }

                testMockObject.AppendLine(property.Name + " = (" + property.PropertyType + ")\"" + property.GetValue(obj, null) + "\",");
            }

            testMockObject.AppendLine("});");
        }
    }

It's possible the object will have a TypeConverter that supports conversion to InstanceDescriptor , which is what the WinForms designer uses when emitting C# code to generate an object.对象可能有一个支持转换为InstanceDescriptor的 TypeConverter ,这是 WinForms 设计器在发出 C# 代码以生成对象时使用的。 If it can't convert to an InstanceDescriptor, it will attempt to use a parameterless constructor and simply set public properties.如果它无法转换为 InstanceDescriptor,它将尝试使用无参数构造函数并简单地设置公共属性。 The InstanceDescriptor mechanism is handy, since it allows you to specify various construction options such as constructors with parameters or even static factory method calls. InstanceDescriptor 机制很方便,因为它允许您指定各种构造选项,例如带参数的构造函数甚至静态工厂方法调用。

I have some utility code I've written that emits loading of an in-memory object using IL, which basically follows the above pattern (use InstanceDescriptor if possible and, if not, simply write public properties.) Note that this will only produce an equivalent object if the InstanceDescriptor is properly implemented or setting public properties is sufficient to restore object state.我编写了一些使用 IL 加载内存中对象的实用程序代码,它基本上遵循上述模式(如果可能,请使用 InstanceDescriptor,如果没有,只需编写公共属性。)请注意,这只会产生一个如果 InstanceDescriptor 正确实现或设置公共属性足以恢复对象状态,则等效对象。 If you're emitting IL, you can also cheat and read/write field values directly (this is what the DataContractSerializer supports), but there are a lot of nasty corner cases to consider.如果您要发出 IL,您也可以直接作弊和读/写字段值(这是 DataContractSerializer 支持的),但有很多令人讨厌的极端情况需要考虑。

I'm a novice at this as well, but I also needed to take a C# object that defined a hierarchy and extract it to an object initializer to ease setting up a unit test.我也是这方面的新手,但我还需要使用定义层次结构的 C# 对象并将其提取到对象初始值设定项以简化单元测试的设置。 I borrowed heavily from the above and wound up with this.我从上面大量借用并结束了这个。 I'd like to improve the way it handles recognizing user classes.我想改进它处理识别用户类的方式。

http://github.com/jefflomax/csharp-object-to-object-literal/blob/master/Program.cs http://github.com/jefflomax/csharp-object-to-object-literal/blob/master/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObjectInitializer
{
    public class Program
    {
        public enum Color { Red, Green, Blue, Yellow, Fidget } ;

        public class Foo
        {
            public int FooId { get; set; }
            public string FooName { get; set; }
        }

        public class Thing
        {
            public int ThingId { get; set; }
            public string ThingName { get; set; }
            public List<Foo> Foos { get; set; }
        }

        public class Widget
        {
            public long Sort { get; set; }
            public char FirstLetter { get; set; }
        }

        public class TestMe
        {
            public Color Color { get; set; }
            public long Key { get; set; }
            public string Name { get; set; }
            public DateTime Created { get; set; }
            public DateTime? NCreated { get; set; }
            public bool Deleted { get; set; }
            public bool? NDeleted { get; set; }
            public double Amount { get; set; }
            public Thing MyThing { get; set; }
            public List<Thing> Things { get; set; }
            public List<Widget> Widgets { get; set; }
        }

        static void Main(string[] args)
        {
            var testMe = new TestMe
            {
                Color = Program.Color.Blue,
                Key = 3,
                Name = "SAK",
                Created = new DateTime(2013,10,20,8,0,0),
                NCreated = (DateTime?)null,
                Deleted = false,
                NDeleted = null,
                Amount = 13.1313,
                MyThing = new Thing(){ThingId=1,ThingName="Thing 1"},
                Things = new List<Thing>
                {
                    new Thing
                    {
                        ThingId=4,
                        ThingName="Thing 4",
                        Foos = new List<Foo>
                        {
                            new Foo{FooId=1, FooName="Foo 1"},
                            new Foo{FooId=2,FooName="Foo2"}
                        }
                    },
                    new Thing
                    {
                        ThingId=5,
                        ThingName="Thing 5",
                        Foos = new List<Foo>()
                    }
                },
                Widgets = new List<Widget>()
            };

            var objectInitializer = ToObjectInitializer(testMe);
            Console.WriteLine(objectInitializer);

            // This is the returned C# Object Initializer
            var x = new TestMe { Color = Program.Color.Blue, Key = 3, Name = "SAK", Created = new DateTime(2013, 10, 20, 8, 0, 0), NCreated = null, Deleted = false, NDeleted = null, Amount = 13.1313, MyThing = new Thing { ThingId = 1, ThingName = "Thing 1", Foos = new List<Foo>() }, Things = new List<Thing> { new Thing { ThingId = 4, ThingName = "Thing 4", Foos = new List<Foo> { new Foo { FooId = 1, FooName = "Foo 1" }, new Foo { FooId = 2, FooName = "Foo2" } } }, new Thing { ThingId = 5, ThingName = "Thing 5", Foos = new List<Foo>() } }, Widgets = new List<Widget>() };
            Console.WriteLine("");
        }

        public static string ToObjectInitializer(Object obj)
        {
            var sb = new StringBuilder(1024);

            sb.Append("var x = ");
            sb = WalkObject(obj, sb);
            sb.Append(";");

            return sb.ToString();
        }

        private static StringBuilder WalkObject(Object obj, StringBuilder sb)
        {
            var properties = obj.GetType().GetProperties();

            var type = obj.GetType();
            var typeName = type.Name;
            sb.Append("new " + type.Name + " {");

            bool appendComma = false;
            DateTime workDt;
            foreach (var property in properties)
            {
                if (appendComma) sb.Append(", ");
                appendComma = true;

                var pt = property.PropertyType;
                var name = pt.Name;

                var isList = property.PropertyType.GetInterfaces().Contains(typeof(IList));

                var isClass = property.PropertyType.IsClass;

                if (isList)
                {
                    IList list = (IList)property.GetValue(obj, null);
                    var listTypeName = property.PropertyType.GetGenericArguments()[0].Name;

                    if (list != null && list.Count > 0)
                    {
                        sb.Append(property.Name + " = new List<" + listTypeName + ">{");
                        sb = WalkList( list, sb );
                        sb.Append("}");
                    }
                    else
                    {
                        sb.Append(property.Name + " = new List<" + listTypeName + ">()");
                    }
                }
                else if (property.PropertyType.IsEnum)
                {
                    sb.AppendFormat("{0} = {1}", property.Name, property.GetValue(obj));
                }
                else
                {
                    var value = property.GetValue(obj);
                    var isNullable = pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>);
                    if (isNullable)
                    {
                        name = pt.GetGenericArguments()[0].Name;
                        if (property.GetValue(obj) == null)
                        {
                            sb.AppendFormat("{0} = null", property.Name);
                            continue;
                        }
                    }

                    switch (name)
                    {
                        case "Int64":
                        case "Int32":
                        case "Int16":
                        case "Double":
                        case "Float":
                            sb.AppendFormat("{0} = {1}", property.Name, value);
                            break;
                        case "Boolean":
                            sb.AppendFormat("{0} = {1}", property.Name, Convert.ToBoolean(value) == true ? "true" : "false");
                            break;
                        case "DateTime":
                            workDt = Convert.ToDateTime(value);
                            sb.AppendFormat("{0} = new DateTime({1},{2},{3},{4},{5},{6})", property.Name, workDt.Year, workDt.Month, workDt.Day, workDt.Hour, workDt.Minute, workDt.Second);
                            break;
                        case "String":
                            sb.AppendFormat("{0} = \"{1}\"", property.Name, value);
                            break;
                        default:
                            // Handles all user classes, should likely have a better way
                            // to detect user class
                            sb.AppendFormat("{0} = ", property.Name);
                            WalkObject(property.GetValue(obj), sb);
                            break;
                    }
                }
            }

            sb.Append("}");

            return sb;
        }

        private static StringBuilder WalkList(IList list, StringBuilder sb)
        {
            bool appendComma = false;
            foreach (object obj in list)
            {
                if (appendComma) sb.Append(", ");
                appendComma = true;
                WalkObject(obj, sb);
            }

            return sb;
        }
    }
}

I stumbled across this while looking for the same kind of method Matthew described, and was inspired by Evan's answer to write my own extension method.我在寻找 Matthew 所描述的相同类型的方法时偶然发现了这一点,并受到 Evan 的回答的启发,编写了我自己的扩展方法。 It generates compilable C# code as a string that can be copy/pasted into Visual Studio.它将可编译的 C# 代码生成为可以复制/粘贴到 Visual Studio 的字符串。 I didn't bother with any particular formatting and just output the code on one line and use ReSharper to format it nicely.我没有打扰任何特定的格式,只是在一行上输出代码并使用 ReSharper 很好地对其进行格式化。 I've used it with some big DTOs that we were passing around and so far it works like a charm.我已经将它与我们正在传递的一些大型 DTO 一起使用,到目前为止,它的作用就像一个魅力。

Here's the extension method and a couple helper methods:这是扩展方法和几个辅助方法:

public static string ToCreationMethod(this object o)
{
    return String.Format("var newObject = {0};", o.CreateObject());
}

private static StringBuilder CreateObject(this object o)
{
    var builder = new StringBuilder();
    builder.AppendFormat("new {0} {{ ", o.GetClassName());

    foreach (var property in o.GetType().GetProperties())
    {
        var value = property.GetValue(o);
        if (value != null)
        {
            builder.AppendFormat("{0} = {1}, ", property.Name, value.GetCSharpString());
        }
    }

    builder.Append("}");
    return builder;
}

private static string GetClassName(this object o)
{
    var type = o.GetType();

    if (type.IsGenericType)
    {
        var arg = type.GetGenericArguments().First().Name;
        return type.Name.Replace("`1", string.Format("<{0}>", arg));
    }

    return type.Name;
}

The method GetCSharpString contains the logic, and it's open to extension for any particular type.方法 GetCSharpString 包含逻辑,它对任何特定类型的扩展都是开放的。 It was enough for me that it handled strings, ints, decimals, dates anything that implements IEnumerable:对我来说,它处理字符串、整数、小数、日期任何实现 IEnumerable 的东西就足够了:

private static string GetCSharpString(this object o)
{
    if (o is String)
    {
        return string.Format("\"{0}\"", o);
    }
    if (o is Int32)
    {
        return string.Format("{0}", o);
    }
    if (o is Decimal)
    {
        return string.Format("{0}m", o);
    }
    if (o is DateTime)
    {
        return string.Format("DateTime.Parse(\"{0}\")", o);
    }
    if (o is IEnumerable)
    {
        return String.Format("new {0} {{ {1}}}", o.GetClassName(), ((IEnumerable)o).GetItems());
    }

    return string.Format("{0}", o.CreateObject());
}

private static string GetItems(this IEnumerable items)
{
    return items.Cast<object>().Aggregate(string.Empty, (current, item) => current + String.Format("{0}, ", item.GetCSharpString()));
}

I hope someone finds this useful!我希望有人觉得这很有用!

It may comes a bit late, but here is my 5cents on that problem.它可能来得有点晚,但这是我在这个问题上的 5 美分。

The mentioned Visual Studio Extension (OmarElabd/ObjectExporter) was a good idea, but I needed to generate C# code from in-memory objects at runtime, during unit test execution.提到的 Visual Studio 扩展 (OmarElabd/ObjectExporter) 是一个好主意,但我需要在运行时、单元测试执行期间从内存中的对象生成 C# 代码。 This is what evolved from the original problem: https://www.nuget.org/packages/ObjectDumper.NET/这是从原始问题演变而来的: https : //www.nuget.org/packages/ObjectDumper.NET/

ObjectDumper.Dump(obj, DumpStyle.CSharp); ObjectDumper.Dump(obj, DumpStyle.CSharp); returns C# initializer code from a variable.从变量返回 C# 初始值设定项代码。 Please let me know if you find issues, you might want to report them on github.如果您发现问题,请告诉我,您可能想在 github 上报告它们。

There's a solution similar to what Evan proposed , but a bit better suited for my particular task.有一个类似于Evan 提出的解决方案,但更适合我的特定任务。

After playing a bit with CodeDOM and Reflection it turned out that it would be too complicated in my case.在玩了一点 CodeDOM 和 Reflection 之后,结果证明在我的情况下它太复杂了。

The object was serialized as XML, so the natural solution was to use XSLT to simply transform it to the object creation expression.对象被序列化为 XML,因此自然的解决方案是使用 XSLT 将其简单地转换为对象创建表达式。

Sure, it covers only certain types of the cases, but maybe will work for someone else.当然,它只涵盖某些类型的案例,但也许对其他人有用。

Here is an update to @revlucio's solution that adds support for booleans and enums.这是@revlucio 解决方案的更新,增加了对布尔值和枚举的支持。

public static class ObjectInitializationSerializer
{
    private static string GetCSharpString(object o)
    {
        if (o is bool)
        {
            return $"{o.ToString().ToLower()}";
        }
        if (o is string)
        {
            return $"\"{o}\"";
        }
        if (o is int)
        {
            return $"{o}";
        }
        if (o is decimal)
        {
            return $"{o}m";
        }
        if (o is DateTime)
        {
            return $"DateTime.Parse(\"{o}\")";
        }
        if (o is Enum)
        {
            return $"{o.GetType().FullName}.{o}";
        }
        if (o is IEnumerable)
        {
            return $"new {GetClassName(o)} \r\n{{\r\n{GetItems((IEnumerable)o)}}}";
        }

        return CreateObject(o).ToString();
    }

    private static string GetItems(IEnumerable items)
    {
        return items.Cast<object>().Aggregate(string.Empty, (current, item) => current + $"{GetCSharpString(item)},\r\n");
    }

    private static StringBuilder CreateObject(object o)
    {
        var builder = new StringBuilder();
        builder.Append($"new {GetClassName(o)} \r\n{{\r\n");

        foreach (var property in o.GetType().GetProperties())
        {
            var value = property.GetValue(o);
            if (value != null)
            {
                builder.Append($"{property.Name} = {GetCSharpString(value)},\r\n");
            }
        }

        builder.Append("}");
        return builder;
    }

    private static string GetClassName(object o)
    {
        var type = o.GetType();

        if (type.IsGenericType)
        {
            var arg = type.GetGenericArguments().First().Name;
            return type.Name.Replace("`1", $"<{arg}>");
        }

        return type.Name;
    }

    public static string Serialize(object o)
    {
        return $"var newObject = {CreateObject(o)};";
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM