Come posso serializzare un object sul codice inizializzatore dell’object C #?

Sto cercando di prendere un object in memoria (o serializzazione JSON di un object) ed emettere il codice C # per produrre un object equivalente.

Ciò sarebbe utile per estrarre esempi noti da un repository da utilizzare come punti di partenza nei test unitari. Abbiamo preso in considerazione la deserializzazione di JSON, ma il codice C # avrebbe un vantaggio quando si tratta di refactoring.

Se il tuo modello è semplice, puoi utilizzare reflection e un generatore di stringhe per generare direttamente C #. Ho fatto questo per popolare i dati del test unitario esattamente come hai discusso.

L’esempio di codice riportato di seguito è stato scritto in pochi minuti e ha generato un inizializzatore di oggetti che necessitava di qualche ritouch manuale. È ansible scrivere una funzione più robusta / meno bacile se si pianifica di farlo molto.

La seconda funzione è ricorsiva, iterando su tutte le Liste all’interno dell’object e generando il codice anche per quelle.

Disclaimer: questo ha funzionato per il mio modello semplice con tipi di dati di base. Ha generato codice che necessitava di pulizia, ma mi ha permesso di andare avanti rapidamente. È qui solo per servire da esempio di come ciò potrebbe essere fatto. Spero che ispiri qualcuno a scrivere il proprio.

Nel mio caso, ho avuto un’istanza di questo grande set di dati (risultati) che è stato caricato dal database. Per rimuovere la dipendenza del database dal test dell’unità, ho passato l’object a questa funzione che sputava il codice che mi permetteva di prendere in giro l’object nella mia class di 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'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("});"); } } 

C’è un’interessante estensione di Visual Studio che risolve questo problema; l’ Esportatore di oggetti . Permette la serializzazione di un object in memoria nel codice di inizializzazione dell’object C #, JSON e XML. Non l’ho ancora provato, ma sembra intrigante; si aggiornerà dopo averlo provato.

Anche io sono alle prime armi, ma dovevo anche prendere un object C # che definisse una gerarchia ed estrailo a un inizializzatore di oggetti per facilitare l’impostazione di un test unitario. Ho preso in prestito pesantemente da quanto sopra e ho finito con questo. Mi piacerebbe migliorare il modo in cui gestisce il riconoscimento delle classi utente.

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 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 Things { get; set; } public List 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 { new Thing { ThingId=4, ThingName="Thing 4", Foos = new List { new Foo{FooId=1, FooName="Foo 1"}, new Foo{FooId=2,FooName="Foo2"} } }, new Thing { ThingId=5, ThingName="Thing 5", Foos = new List() } }, Widgets = new List() }; 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() }, Things = new List { new Thing { ThingId = 4, ThingName = "Thing 4", Foos = new List { new Foo { FooId = 1, FooName = "Foo 1" }, new Foo { FooId = 2, FooName = "Foo2" } } }, new Thing { ThingId = 5, ThingName = "Thing 5", Foos = new List() } }, Widgets = new List() }; 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; } } } 

Mi sono imbattuto in questo mentre cercavo lo stesso tipo di metodo descritto da Matthew, ed è stato ispirato dalla risposta di Evan per scrivere il mio metodo di estensione. Genera codice C # compilabile come una stringa che può essere copiata / incollata in Visual Studio. Non mi sono preoccupato di alcuna formattazione particolare e ho appena prodotto il codice su una riga e uso ReSharper per formattarlo correttamente. L’ho usato con alcuni grandi DTO che stavamo passando e finora funziona come un incantesimo.

Ecco il metodo di estensione e un paio di metodi di supporto:

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

Il metodo GetCSharpString contiene la logica ed è aperto all’estensione per qualsiasi tipo particolare. Mi è bastato che gestisse stringhe, interi, decimali, date tutto ciò che 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().Aggregate(string.Empty, (current, item) => current + String.Format("{0}, ", item.GetCSharpString())); } 

Spero che qualcuno lo trovi utile!

È ansible che l’object abbia un TypeConverter che supporta la conversione in InstanceDescriptor , che è ciò che il designer WinForms utilizza quando emette il codice C # per generare un object. Se non può essere convertito in un InstanceDescriptor, tenterà di utilizzare un costruttore senza parametri e semplicemente di impostare le proprietà pubbliche. Il meccanismo InstanceDescriptor è utile, poiché consente di specificare varie opzioni di costruzione come costruttori con parametri o anche chiamate di metodi factory statici.

Ho un codice di utilità che ho scritto che emette il caricamento di un object in memoria usando IL, che fondamentalmente segue il modello sopra (usa InstanceDescriptor se ansible e, in caso contrario, scrivi semplicemente proprietà pubbliche.) Nota che questo produrrà solo un object equivalente se InstanceDescriptor è implementato correttamente o l’impostazione di proprietà pubbliche è sufficiente per ripristinare lo stato dell’object. Se stai emettendo IL, puoi anche imbrogliare e leggere / scrivere i valori dei campi direttamente (questo è ciò che supporta DataContractSerializer), ma ci sono molti casi d’angolo sgradevoli da considerare.

C’è una soluzione simile a quella proposta da Evan , ma un po ‘più adatta per il mio particolare compito.

Dopo aver giocato un po ‘con CodeDOM e Reflection, è risultato che nel mio caso sarebbe stato troppo complicato.

L’object è stato serializzato come XML, quindi la soluzione naturale era usare XSLT per trasformarlo semplicemente nell’espressione di creazione dell’object.

Certo, copre solo alcuni tipi di casi, ma forse funzionerà per qualcun altro.

Ecco un aggiornamento alla soluzione di @ revlucio che aggiunge il supporto per booleani ed enumerazioni.

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