Инициализаторы свойств / полей при генерации кода - PullRequest
0 голосов
/ 10 января 2012

Я генерирую код в расширении visual studio с использованием CodeDom и строк простого кода.Мое расширение читает текущие классы объявленных полей и свойств, используя отражение, и генерирует конструкторы, инициализаторы, реализует определенные интерфейсы и т. Д.

Класс генератора прост:

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.Name возвращает List`1.Здесь есть две проблемы.Во-первых, мне нужно получить правильное имя для универсального типа без использования строковых манипуляций.Во-вторых, мне нужно получить доступ к базовому типу.Полное имя типа свойства возвращает что-то вроде:

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

Ответы [ 2 ]

1 голос
/ 10 января 2012

Прежде чем я попытаюсь ответить, я вынужден указать, что то, что вы делаете, кажется излишним.Предполагая, что вы помещаете этот код в конструктор, генерировать что-то вроде:

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 ссылочного типа равно null,поэтому

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

не создает новый List<int>, он просто устанавливает this.list в null.Я подозреваю, что вы хотите сделать вместо этого, использовать свойство Type.IsValueType, чтобы оставить типы значений в их значениях по умолчанию, и инициализировать ссылочные типы, используя new.

Наконец, я думаю, что выздесь ищем свойство IsGenericType класса Type и соответствующий метод 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 + ")";  
  }
}

РЕДАКТИРОВАТЬ:

Что касается конструирования чего-то полезного для ссылочного типа, то, как я видел, используемый сгенерированный код распространенный метод - требовать конструктор без параметров для любого класса, который вы ожидаете использовать.Достаточно просто увидеть, есть ли у класса конструктор без параметров, вызвав Type.GetConstructor(), передав пустой Type[] (например, Type.EmptyTypes), и посмотреть, вернет ли он ConstructorInfo или null.Как только это будет установлено, просто заменив default(typename) на new typename(), вы получите то, что вам нужно.

В более общем случае вы можете предоставить любой массив типов этому методу, чтобы увидеть, есть ли соответствующий конструктор, или вызвать GetConstructors() чтобы получить их всех.Здесь нужно обратить внимание на поля IsPublic, IsStatic и IsGenericMethod поля ConstructorInfo, чтобы найти поле, которое вы действительно можете вызвать из любого места, где генерируется этот код.

Проблемавы пытаетесь решить, однако, он станет произвольно сложным, если вы не можете наложить на него некоторые ограничения.Одним из вариантов может быть поиск произвольного конструктора и создание вызова, который выглядит следующим образом:

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

(Обратите внимание, что это только для иллюстративных целей, я бы, вероятно, использовал здесь CodeDOM или, по крайней мере, StringBuilder:)

Но, конечно, теперь у вас есть проблема определения подходящего имени типа для каждого параметра, которые сами могут быть обобщенными.И все параметры ссылочного типа будут инициализированы нулем.И нет никакого способа узнать, какой из произвольно большого количества конструкторов, из которых вы можете выбрать, на самом деле производит полезный объект (некоторые из них могут делать плохие вещи, например, предположить, что вы собираетесь устанавливать свойства или вызывать методы сразу после создания экземпляра).

То, как вы решаете эти проблемы, не является техническим: вы можете рекурсивно применять эту же логику к каждому параметру настолько низко, насколько захотите.Для вашего случая использования необходимо решить, насколько сложными вам должны быть и какие ограничения вы хотите установить для пользователей.

1 голос
/ 10 января 2012

Если вы уверены, что хотите использовать строки, вам придется написать свой собственный метод для форматирования этих имен типов. Что-то вроде:

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 s в вашем файле.

Но я думаю, что гораздо лучше использовать объектную модель CodeDOM. Таким образом, вам не нужно беспокоиться о using s, типах форматирования или опечатках:

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

И если вы действительно не хотите использовать default(T), вы можете узнать, является ли тип ссылочным или типом значения. Если это ссылочный тип, используйте null. Если это тип значения, конструктор по умолчанию должен существовать, и вы можете вызвать его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...