2011-04-26 25 views
31

Estoy buscando tomar un objeto en memoria (o serialización JSON de un objeto) y emitir código C# para producir un objeto equivalente.¿Cómo puedo serializar un objeto al código del inicializador del objeto C#?

Esto sería útil para extraer ejemplos conocidos de un repositorio para utilizar como puntos de partida en pruebas unitarias. Hemos considerado deserializar JSON, pero el código C# tendría una ventaja cuando se trata de refactorización.

+0

Supongo que hay una razón por la que no puede usar el xml serlizer. – rerun

+2

Ciertamente podemos y podemos. Pero el código sería preferible a XML por la misma razón que mencioné con respecto a JSON; fácil refactorización –

Respuesta

9

Si su modelo es simple, puede usar el reflejo y un generador de cadenas para generar C# directamente. He hecho esto para completar datos de pruebas de unidad exactamente como lo discutió.

El siguiente ejemplo de código se escribió en unos minutos y generó un inicializador de objetos que necesitaba un ajuste manual. Una función más robusta/con menos errores podría escribirse si planea hacer esto mucho.

La segunda función es recursiva, iterando sobre cualquier lista dentro del objeto y generando código para esas también.

Descargo de responsabilidad: Esto funcionó para mi modelo simple con tipos de datos básicos. Se generó un código que necesitaba limpieza, pero me permitió avanzar rápidamente. Está solo aquí para servir como un ejemplo de cómo se podría hacer esto. Con suerte, inspira a alguien a escribir el suyo.

En mi caso, tuve una instancia de este gran conjunto de datos (resultados) que se cargó desde la base de datos. Para eliminar la dependencia de la base de datos de mi prueba unitaria, le di el objeto a esta función, que escupió el código que me permitía simular el objeto en mi clase de prueba.

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

Es posible que el objeto tenga un TypeConverter que admita la conversión a InstanceDescriptor, que es lo que el diseñador de WinForms usa al emitir código C# para generar un objeto. Si no puede convertirse en un InstanceDescriptor, intentará usar un constructor sin parámetros y simplemente establecer las propiedades públicas. El mecanismo InstanceDescriptor es útil, ya que le permite especificar varias opciones de construcción, como constructores con parámetros o incluso llamadas a métodos estáticos de fábrica.

Tengo un código de utilidad que he escrito que emite la carga de un objeto en memoria usando IL, que básicamente sigue el patrón anterior (use InstanceDescriptor si es posible y, si no, simplemente escriba propiedades públicas). solo producirá un objeto equivalente si el InstanceDescriptor se implementa correctamente o si la configuración de propiedades públicas es suficiente para restaurar el estado del objeto. Si está emitiendo IL, también puede copiar y leer directamente los valores de campo (esto es lo que DataContractSerializer admite), pero hay muchos casos de esquina desagradables que considerar.

+0

Más detalles sobre cómo hacer esto serían geniales. Estoy especialmente interesado en hacerlo sin un convertidor de tipo personalizado – Schneider

+0

Detalles sobre cómo hacer qué parte? –

+0

usando InstanceDescriptor para generar el código – Schneider

1

Hay una solución similar a what Evan proposed, pero un poco más adecuado para mi tarea en particular.

Después de jugar un poco con CodeDOM y Reflection resultó que sería demasiado complicado en mi caso.

El objeto se serializó como XML, por lo que la solución natural fue usar XSLT para simplemente transformarlo a la expresión de creación de objeto.

Claro, solo cubre ciertos tipos de casos, pero tal vez funcione para otra persona.

5

Soy un novato en esto también, pero también necesitaba tomar un objeto C# que definiera una jerarquía y la extrajera en un inicializador de objetos para facilitar la configuración de una prueba unitaria. Pedí prestado mucho de lo anterior y terminé con esto. Me gustaría mejorar la forma en que maneja el reconocimiento de clases de usuarios.

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

¡Bastante bien! ¡Obviamente poco nervioso pero una gran herramienta sin embargo! – Mrchief

5

me encontré con este mientras se busca el mismo tipo de método de Mateo describe, y fue inspirado por la respuesta de Evan escribir mi propio método de extensión. Genera código C# compilable como una cadena que se puede copiar/pegar en Visual Studio. No me molesté con ningún formato en particular y acabo de sacar el código en una línea y usar ReSharper para formatearlo bien. Lo he usado con algunos DTO grandes que estábamos pasando y hasta ahora funciona a las mil maravillas.

Aquí está el método de extensión y un par métodos de ayuda:

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

El GetCSharpString método contiene la lógica, y está abierto a la extensión de cualquier tipo particular. Fue suficiente para mí que maneja cadenas, enteros, decimales, data nada que implementa 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())); 
} 

espero que alguien encuentre esto útil!

8

Hay una interesante extensión de Visual Studio que soluciona esto; el Object Exporter. Permite la serialización de un objeto en memoria en el código de inicialización del objeto C#, JSON y XML. No lo he intentado todavía pero parece intrigante; se actualizará después de probarlo.

0

Aquí hay una actualización de la solución @ revlucio que agrega soporte para booleanos y enums.

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