2012-01-10 3 views
0

CodeDom과 일반 코드 문자열을 사용하여 Visual Studio 확장 코드를 생성하고 있습니다. 내 확장, 현재의 클래스 선언 된 필드와 반사를 사용하여 속성을 읽고 contructors, 이니셜 라이저를 생성, 특정 인터페이스를 구현하는 등코드 생성의 속성/필드 초기화 자

발전기 클래스는 간단하다 :

public class CodeGenerator <T> 
{ 
    public string GetCode() 
    { 
     string code = ""; 
     T type = typeof(T); 
     List <PropertyInfo> properties = t.GetProperties(); 
     foreach (PropertyInfo property in properties) 
      code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")"; 
    } 
} 

내가 필드와 특성 초기화에 붙어 두 가지면에서.

첫째, 대부분의 경우 default(AnyNonGenericValueOrReferenceType)이 작동하는 것처럼 보일지라도 생성 된 코드에서 사용하는 것이 불편합니다.

둘째, 제네릭 형식의 기본 형식을 가져 오는 방법을 찾을 수 없기 때문에 제네릭 형식에서는 작동하지 않습니다. 따라서 속성이 List <int> 인 경우 property.PropertyType.NameList`1을 반환합니다. 여기에는 두 가지 문제점이 있습니다. 첫째, 문자열 조작을 사용하지 않고 제네릭 형식에 적절한 이름을 지정해야합니다. 둘째, 기본 유형에 액세스해야합니다. 전체 속성 유형 이름은 다음과 같이 반환됩니다.

 
System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] 
+0

'default (T)'를 사용하는 것이 왜 불편한 것인지 자세히 설명해 주시겠습니까? – svick

+0

그것은 진부함이 아니지만 일반 사용자가 친숙 할 수있는 이니셜 라이저를 생성하기 위해 CodeDom을 사용하고 싶습니다. 여전히 기본 (T) 구성에 비틀 거릴 제네릭의 깊이를 보지 못한 많은 개발자가 있습니다. –

+0

그리고 왜이 코드를 생성하고 있습니까? 모든 필드는 생성자가 실행되기 전에 기본값으로 설정됩니다. 여기에는 자동 속성의 백업 필드가 포함됩니다. – svick

답변

1

문자열을 사용하려는 경우 해당 유형 이름의 형식을 지정해야합니다. 뭔가 같이 :

static string FormatType(Type t) 
{ 
    string result = t.Name; 

    if (t.IsGenericType) 
    { 
     result = string.Format("{0}<{1}>", 
      result.Split('`')[0], 
      string.Join(",", t.GetGenericArguments().Select(FormatType))); 
    } 

    return result; 
} 

이 코드는 파일에 필요한 모든 using들 있다고 가정합니다.

하지만 실제로 CodeDOM의 개체 모델을 사용하는 것이 훨씬 더 좋습니다. 이 방법을 사용하면 유형 또는 오타 포맷에 대한 using들 걱정할 필요가 없습니다 :

var statement = 
    new CodeAssignStatement(
     new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), property.Name), 
     new CodeDefaultValueExpression(new CodeTypeReference(property.PropertyType))); 

을 그리고 당신이 정말로 default(T)을 사용하지 않으려는 경우 유형이 참조 또는 값인지, 당신은 밖으로 찾을 수 있습니다 유형. 참조 유형 인 경우 null을 사용하십시오. 값 유형이면 기본 생성자가 있어야하므로 호출 할 수 있습니다.

+0

감사합니다. 나는 당신의 CodeDom에게 일반적인 레퍼런스 타입으로 어떻게 사용되는지를 알려줄 것이다. 나는 중첩 된 제네릭으로 붙어있을 것 같은 느낌이 든다. –

+1

내 솔루션 모두 내포 된 제네릭과 잘 작동해야합니다. – svick

+0

지금까지 첫 번째 접근 방식은 완벽하게 작동했습니다. 전체 네임 스페이스를 추가하고 'System.Collections.Generic.List >'을 얻었습니다. 'Entity'를 어떻게 완전 자격 할 수 있습니까? 이제 CodeDom을 사용하여 복잡한 생성자에 대한 올바른 호출 생성을 처리하는지 확인합니다. –

1

내가 대답하려고하기 전에, 나는 당신이하는 일이 불필요한 것으로 지적해야한다고 생각합니다. 이 코드를 생성자에 넣으면 다음과 같이 생성됩니다.

public class Foo 
{ 
    private int a; 
    private bool b; 
    private SomeType c; 

    public Foo() 
    { 
    this.a = default(int); 
    this.b = default(bool); 
    this.c = default(SomeType); 
    } 
} 

은 필요하지 않습니다. 클래스가 생성되면 이 이미 자동으로 발생합니다. 실제로 JITter가 처리 할 수 ​​있다고 가정 할지라도 실제로는 몇 가지 간단한 테스트를 통해 이러한 할당이 최적화되지 않은 것으로 나타납니다.

두 번째로 default 키워드가 설계되었습니다. 컴파일 할 때 유형을 알 수없는 변수에 "기본"값을 할당하는 방법을 제공하는 것이 중요합니다. 그것은 일반적인 코드에 의해 사용하기 위해 도입 된 것으로 추측되지만, 자동 생성 된 코드는이 코드를 사용할 때 확실히 맞습니다. 참조 형의 default 값이 너무, 새로운 List<int>을 구성하지 않습니다

this.list = default(List<int>); 

null 것을

이 명심, 그냥 nullthis.list을 설정합니다.당신이하고 싶다고 생각하는 대신 Type.IsValueType 속성을 사용하여 기본값에 값 유형을두고 new을 사용하여 참조 유형을 초기화합니다.

마지막으로, 나는 당신이 여기에서 찾고있는 것은 Type 클래스의 IsGenericType 속성과 해당 GetGenericArguments() 방법이라고 생각 :

foreach (PropertyInfo property in properties) 
{ 
    if (property.Type.IsGenericType) 
    { 
    var subtypes = property.Type.GetGenericArguments(); 
    // construct full type name from type and subtypes. 
    } 
    else 
    { 
    code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")"; 
    } 
} 

편집 : 지금까지 A에 대한 유용한 뭔가를 구성하는 등

참조 형식, 생성 된 코드에서 사용하는 일반적인 기술은 사용할 클래스의 매개 변수없는 생성자를 요구하는 것입니다. Type.GetConstructor()을 호출하고 빈 Type[] (예 : Type.EmptyTypes)을 전달하고 매개 변수없는 생성자가 있는지 클래스를 확인하고 ConstructorInfo 또는 null을 반환하는지 확인하는 것은 쉽습니다. 일단 설정이 완료되면 default(typename)new typename()으로 바꾸면 필요한 것을 얻을 수 있습니다.

더 일반적으로 일치하는 생성자가 있는지 확인하기 위해 해당 메서드에 대한 모든 배열 배열을 제공하거나 모두 GetConstructors()을 호출하여 모두 가져올 수 있습니다. 여기서주의해야 할 점은 IsPublic, IsStaticIsGenericMethod 필드가 ConstructorInfo인데이 코드가 생성되는 곳 어디에서나 실제로 호출 할 수있는 코드를 찾으십시오.

해결하려는 문제는 일부 제약 조건을 적용 할 수없는 이상 임의로 복잡해집니다. 하나의 옵션은 임의의 생성자를 찾아 다음과 같다 전화를 구축하는 것입니다 :

var line = "this." + fieldName + " = new("; 
foreach (var param in constructor.GetParameters()) 
{ 
    line += "default(" + param.ParameterType.Name + "),"; 
} 
line = line.TrimEnd(',') + ");" 

(아마 여기 된 CodeDom 사용하는 것, 이것은 단지 설명을위한 참고, 또는 적어도 모두 StringBuilder :

물론 지금은 generics가 될 수있는 각 매개 변수에 대해 적절한 유형 이름을 결정해야하는 문제가 있습니다. 그리고 참조 유형 매개 변수는 모두 null로 초기화됩니다. 그리고 당신이 선택할 수있는 임의의 많은 생성자 중 실제로 사용할 수있는 객체를 생성하는 방법을 알 수있는 방법이 없습니다. 인스턴스를 생성 한 직후에 속성을 설정하거나 메서드를 호출한다고 가정하는 것과 같이 나쁜 것들이있을 수 있습니다.

이러한 문제를 해결하는 방법은 기술적 인 것이 아닙니다. 사용자가 원하는만큼 멀리까지 각 매개 변수에 동일한 논리를 반복적으로 적용 할 수 있습니다. 사용 사례에 따라 사용자가 얼마나 복잡해야하며 어떤 종류의 제한을 사용자에게 제공할지 결정해야합니다.

+0

어떤 종류의 코드 분석에 대해 이야기하고 있습니까? – svick

+1

side-note : 누구나 SEML에서 백틱을 벗어나는 방법을 알고 있습니까? –

+1

Foo'1을 사용할 수 있습니다. – svick

관련 문제