[英]How can I serialize an object to C# object initializer code?
我正在尋找一個內存對象(或對象的 JSON 序列化)並發出 C# 代碼來生成等效的對象。
這對於從存儲庫中提取已知良好的示例以用作單元測試的起點非常有用。 我們考慮過反序列化 JSON,但 C# 代碼在重構方面具有優勢。
有一個有趣的 Visual Studio 擴展可以解決這個問題; 對象導出器。 它允許將內存中的對象序列化為 C# 對象初始化代碼、JSON 和 XML。 我還沒有嘗試過,但看起來很有趣; 試用后會更新。
如果您的模型很簡單,您可以使用反射和字符串生成器直接輸出 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 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("});");
}
}
對象可能有一個支持轉換為InstanceDescriptor的 TypeConverter ,這是 WinForms 設計器在發出 C# 代碼以生成對象時使用的。 如果它無法轉換為 InstanceDescriptor,它將嘗試使用無參數構造函數並簡單地設置公共屬性。 InstanceDescriptor 機制很方便,因為它允許您指定各種構造選項,例如帶參數的構造函數甚至靜態工廠方法調用。
我編寫了一些使用 IL 加載內存中對象的實用程序代碼,它基本上遵循上述模式(如果可能,請使用 InstanceDescriptor,如果沒有,只需編寫公共屬性。)請注意,這只會產生一個如果 InstanceDescriptor 正確實現或設置公共屬性足以恢復對象狀態,則等效對象。 如果您要發出 IL,您也可以直接作弊和讀/寫字段值(這是 DataContractSerializer 支持的),但有很多令人討厭的極端情況需要考慮。
我也是這方面的新手,但我還需要使用定義層次結構的 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;
}
}
}
我在尋找 Matthew 所描述的相同類型的方法時偶然發現了這一點,並受到 Evan 的回答的啟發,編寫了我自己的擴展方法。 它將可編譯的 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 美分。
提到的 Visual Studio 擴展 (OmarElabd/ObjectExporter) 是一個好主意,但我需要在運行時、單元測試執行期間從內存中的對象生成 C# 代碼。 這是從原始問題演變而來的: https : //www.nuget.org/packages/ObjectDumper.NET/
ObjectDumper.Dump(obj, DumpStyle.CSharp); 從變量返回 C# 初始值設定項代碼。 如果您發現問題,請告訴我,您可能想在 github 上報告它們。
有一個類似於Evan 提出的解決方案,但更適合我的特定任務。
在玩了一點 CodeDOM 和 Reflection 之后,結果證明在我的情況下它太復雜了。
對象被序列化為 XML,因此自然的解決方案是使用 XSLT 將其簡單地轉換為對象創建表達式。
當然,它只涵蓋某些類型的案例,但也許對其他人有用。
這是@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.