2011-04-26 2 views
31

메모리 내 개체 (또는 개체의 JSON serialization)를 가져 와서 C# 코드를 생성하여 동일한 개체를 생성하려고합니다.개체를 C# 개체 이니셜 라이저 코드로 직렬화하려면 어떻게해야합니까?

이것은 단위 테스트에서 시작점으로 사용하기 위해 저장소에서 잘 알려진 예제를 추출하는 데 유용합니다. JSON을 비 직렬화하는 방법을 고려해 보았지만 리팩토링에 있어서는 C# 코드가 가장 중요합니다.

+0

xml serlizer를 사용할 수없는 이유가 있다고 가정합니다. – rerun

+2

물론 할 수 있습니다. 그러나 JSON에 관해 언급 한 것과 같은 이유 때문에 코드가 XML보다 바람직합니다. 쉬운 리펙토링. –

답변

9

모델이 단순하면 리플렉션과 문자열 작성기를 사용하여 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("});"); 
     } 
    } 
4

개체에 InstanceDescriptor으로의 변환을 지원하는 TypeConverter가있을 수 있습니다. 이것은 WinForms 디자이너가 개체를 생성하기 위해 C# 코드를 내보낼 때 사용하는 것입니다. InstanceDescriptor로 변환 할 수 없으면 매개 변수없는 생성자를 사용하고 공용 속성을 설정하기 만합니다. InstanceDescriptor 메커니즘은 매개 변수가있는 생성자 또는 정적 팩토리 메서드 호출과 같은 다양한 생성 옵션을 지정할 수 있으므로 편리합니다.

위의 패턴을 기본적으로 따르는 IL을 사용하여 메모리 내 개체로드를 내보내는 유틸리티 코드가 있습니다 (가능한 경우 InstanceDescriptor를 사용하고 그렇지 않은 경우 공용 속성을 작성하십시오). InstanceDescriptor가 제대로 구현되었거나 공용 속성을 설정하면 개체 상태를 복원하기에 충분할 경우에만 동등한 개체를 생성합니다. 일리노이를 내보내는 중이라면 필드 값을 직접 속여서 읽고 쓸 수도 있지만 (DataContractSerializer가 지원하는 것임) 고려해야 할 많은 까다로운 사례가 있습니다.

+0

이 작업을 수행하는 방법에 대한 자세한 내용은 훌륭합니다. 나는 특히 custom typeconverter없이이 작업을 수행하는 것에 관심이있다. – Schneider

+0

어떻게해야 하는가? –

+0

InstanceDescriptor를 사용하여 코드 생성 – Schneider

1

조금 더 적합 내 특정 작업이 what Evan proposed 유사한 솔루션,하지만.

CodeDOM 및 Reflection을 사용하여 약간의 연주를 한 후에는 제 경우가 너무 복잡 할 수밖에 없었습니다.

개체가 XML로 직렬화되었으므로 자연스러운 해결책은 XSLT를 사용하여 단순히 개체 생성 식으로 변환하는 것이 었습니다.

확실히 특정 유형의 사례 만 다루지 만 다른 누군가에게는 효과가있을 수 있습니다.

5

나는 초보자이기도하지만 계층을 정의한 C# 개체를 가져 와서 개체 이니셜 라이저로 추출하여 단위 테스트를 쉽게 설정해야했습니다. 나는 위의 것을 많이 빌려와 이것으로 끝내었다. 나는 그것이 사용자 클래스를 인식하는 방법을 개선하고 싶습니다.

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

멋지다! 분명히 작은 날카로운 그러나 그럼에도 불구하고 훌륭한 도구! – Mrchief

5

http://github.com/jefflomax/csharp-object-to-object-literal/blob/master/Program.cs

나는 매튜 설명 방법의 같은 종류를 찾고이있는 동안 우연히 발견하고, 내 자신의 확장 메서드를 작성하는 에반의 대답에 의해 영감을했다. Visual Studio로 복사/붙여 넣기 할 수있는 문자열로 컴파일 가능한 C# 코드를 생성합니다. 나는 특정 형식을 고민하지 않고 한 줄에 코드를 출력하고 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이 로직을 포함하고, 어떤 특정 형식에 대한 확장에 열려.

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

나는 사람이이 유용한 발견 희망 : 그것은 문자열의 int, 소수점을 처리하는 것이 나를 위해 충분했다, IEnumerable을 구현 아무것도 날짜!

8

이 문제를 해결하는 흥미로운 Visual Studio 확장이 있습니다. Object Exporter. 메모리 내 개체를 C# 개체 초기화 코드, JSON 및 XML로 직렬화 할 수 있습니다. 아직 시도하지는 않았지만 흥미 로워 보인다. 시도한 후에 업데이트됩니다.

0

다음은 @ 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)};"; 
    } 
} 
관련 문제