Как я могу сериализовать объект в код инициализатора объекта C #? - PullRequest
39 голосов
/ 26 апреля 2011

Я хочу взять объект в памяти (или сериализацию объекта в формате JSON) и создать код C # для создания эквивалентного объекта.

Это было бы полезно для извлечения известных примеров из репозитория для использования в качестве отправных точек в модульных тестах. Мы рассмотрели десериализацию JSON, но код C # будет иметь преимущество при рефакторинге.

Ответы [ 8 ]

13 голосов
/ 14 мая 2015

Существует интересное расширение Visual Studio, которое решает эту проблему; Экспортер объектов . Это позволяет сериализацию объекта в памяти в код инициализации C # Object, JSON и XML. Я еще не пробовал, но выглядит интригующим; обновит после того, как попробует это.

11 голосов
/ 04 мая 2011

Если ваша модель проста, вы можете использовать отражение и построитель строк для непосредственного вывода C #.Я сделал это, чтобы заполнить данные модульных тестов в точности так, как вы обсуждали.

Пример кода, приведенный ниже, был написан за несколько минут и сгенерировал инициализатор объекта, который нуждался в ручной настройке.Если вы планируете делать это много, можно написать более надежную / менее ошибочную функцию.

Вторая функция является рекурсивной, выполняет итерацию по любым спискам в объекте и генерирует для них код.

Отказ от ответственности: Это работало для моей простой модели с основными типами данных.Он генерировал код, который нуждался в очистке, но позволил мне быстро двигаться дальше.Это только здесь, чтобы служить примером того, как это можно сделать.Надеюсь, это вдохновляет кого-то писать свои собственные.

В моем случае у меня был экземпляр этого большого набора данных (результатов), который был загружен из базы данных.Чтобы удалить зависимость базы данных от моего модульного теста, я передал объект этой функции, которая выплевывала код, который позволял мне высмеивать объект в моем тестовом классе.

    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 it's 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 it's 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("});");
        }
    }
5 голосов
/ 09 мая 2014

Я наткнулся на это, когда искал тот же метод, который описал Мэтью, и был вдохновлен ответом Эвана написать мой собственный метод расширения. Он генерирует скомпилированный код C # в виде строки, которую можно скопировать / вставить в Visual Studio. Я не беспокоился о каком-либо конкретном форматировании, просто выводил код в одну строку и использовал ReSharper для его удобного форматирования. Я использовал его с некоторыми большими DTO, которые мы передавали, и пока он работает как шарм.

Вот метод расширения и пара вспомогательных методов:

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

Метод GetCSharpString содержит логику, и он открыт для расширения для любого конкретного типа. Мне было достаточно, чтобы он обрабатывал строки, целые, десятичные числа, даты и все, что реализует 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()));
}

Я надеюсь, что кто-то найдет это полезным!

5 голосов
/ 21 октября 2013

Я тоже новичок в этом, но мне также нужно было взять объект C #, который определил иерархию, и извлечь его в инициализатор объекта, чтобы упростить настройку модульного теста.Я много позаимствовал из вышесказанного и завелся этим.Я хотел бы улучшить способ распознавания пользовательских классов.

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;
        }
    }
}
5 голосов
/ 26 апреля 2011

Возможно, у объекта будет TypeConverter, который поддерживает преобразование в InstanceDescriptor , что и используется конструктором WinForms при испускании кода C # для генерации объекта. Если он не может преобразоваться в InstanceDescriptor, он попытается использовать конструктор без параметров и просто установить открытые свойства. Механизм InstanceDescriptor удобен, поскольку позволяет указывать различные параметры конструирования, такие как конструкторы с параметрами или даже статические вызовы фабричных методов.

У меня есть некоторый служебный код, который я написал, который испускает загрузку объекта в памяти, используя IL, который в основном следует вышеупомянутому образцу (используйте InstanceDescriptor, если это возможно, и, если нет, просто напишите открытые свойства.) Обратите внимание, что это будет создайте эквивалентный объект только в том случае, если InstanceDescriptor правильно реализован или установки открытых свойств достаточно для восстановления состояния объекта. Если вы излучаете IL, вы также можете напрямую проверять и читать / записывать значения полей (это то, что поддерживает DataContractSerializer), но есть много неприятных угловых случаев, которые следует учитывать.

1 голос
/ 06 марта 2019

Может быть немного поздно, но вот мои 5 центов по этой проблеме.

Упомянутое расширение Visual Studio (OmarElabd / ObjectExporter) было хорошей идеей, но мне нужно было генерировать код C # изобъекты памяти во время выполнения, во время выполнения модульного теста.Вот что произошло с исходной проблемой: https://www.nuget.org/packages/ObjectDumper.NET/

ObjectDumper.Dump (obj, DumpStyle.CSharp);возвращает код инициализатора C # из переменной.Пожалуйста, дайте мне знать, если вы обнаружите проблемы, вы можете сообщить о них на github.

1 голос
/ 17 апреля 2012

Существует решение, подобное , которое предложил Эван , но немного лучше подходящее для моей конкретной задачи.

После небольшой игры с CodeDOM и Reflection оказалось, что это будет слишкомв моем случае это сложно.

Объект был сериализован в XML, поэтому естественным решением было использование XSLT для простого преобразования его в выражение создания объекта.

Конечно, он охватывает только определенные типыслучаи, но, возможно, будет работать для кого-то еще.

0 голосов
/ 03 мая 2017

Вот обновление для решения @ 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)};";
    }
}
...