Подтвердить что ты не робот

Как я могу сериализовать объект на код инициализатора объекта С#?

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

Я наткнулся на это, ища тот же метод, который описал Мэтью, и был вдохновлен ответом Эвана на мой собственный метод расширения. Он генерирует компилируемый код С# в виде строки, которая может быть скопирована/вставлена ​​в 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 содержит логику и открывает расширение для любого конкретного типа. Мне было достаточно, чтобы он обрабатывал строки, ints, десятичные числа, давал все, что реализует 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

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

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

Ответ 6

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

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

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

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

Ответ 7

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