Рефлексия, кодогенерация и обёртка - C#
Формулировка задачи:
Доброго времени суток!
Сначала прикладная задача: имеется статический класс, для которого необходимо делать не статическую обёртку. Руками сделать эту обёртку не приятно, но можно. Но проект живой, развивающийся и статический класс тоже, т.е. в нём время от времени появляются новые члены. А это значит что обёртку нужно постоянно актуализировать, что совсем уж ни в какие ворота.
Я долго думал что с этим делать, рассматривал различные варианты и остановился на следующем: Custom Tool + кодогенерация. Мне кажется это будет идеальным решением. В конечном счёте обёртка всегда актуальная и для этого ничего не надо делать. Если я не прав и есть варианты по лучше, самое время мне сообщить об этом.
Едем дальше. Нагуглил я статей про Custom Tool и примерно представляю как это работает, может быть по ходу ещё возникнут какие-то вопросы, но давайте пока считать этот вопрос решённым. Сейчас же у меня есть вопросы по реализации собственно кодогенератора.
И так, опуская детали, Custom Tool принимает на вход файл, на основе которого мы будем генерить свой файл, обёртку для класса в моём случае. И тут я вижу два пути, простой и правильный.
Простой. Добавляем в кодогенератор референс на мою сборку с оборачиваемым классом, игнорируем входной файл и просто генерим то что нам нужно через рефлексию.
Но хочется сделать по правильному, а по правильному, это не завязываться на конкретную реализацию и сделать универсальный кодогенератор, который можно будет использовать и в будущем для других проектов. А это значит никаких референсов на проект и работать со входным файлом. Отсюда у меня вытекают 2 вопроса:
1. Можно ли как-то заюзать рефлексию имея лишь текстовый файл с классом?
2. Может быть есть какой-то удобный способ парсить cs файл?
Мои навыки гугления не помогли, а парсить файл руками совсем не хочется.
У простого способа есть ещё один недостаток, Custom Tool срабатывает когда изменяется файл к которому он привязан. А рефлексия подозреваю не увидит изменений в классе, пока он не будет скомпилирован. Мелочь, а не приятно.
Решение задачи: «Рефлексия, кодогенерация и обёртка»
textual
Листинг программы
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
namespace Wrappers
{
<#
string projectName = "t4generatewrapper";
string[] classNames = {"t4generatewrapper.ClassToWrap"};
IServiceProvider serviceProvider = (IServiceProvider) this.Host;
EnvDTE.DTE dte = (EnvDTE.DTE) serviceProvider.GetService(typeof(EnvDTE.DTE));
Project currentProject = null;
foreach (Project p in dte.Solution.Projects)
{
if(p.Name == projectName)currentProject = p;
}
foreach (string classFullName in classNames)
{
var classCode = currentProject.CodeModel.CodeTypeFromFullName(classFullName);
#>
public class <#= classFullName.Split('.').Last() #>Wrapper
{
<# foreach (CodeFunction cf in classCode.Members.OfType<CodeFunction>())
{
var pars = string.Join(", ", from CodeParameter p in cf.Parameters select p.Type.AsFullName + " " + p.Name);
var args = string.Join(", ", from CodeParameter p in cf.Parameters select p.Name);
if(cf.Type.AsFullName == "System.Void")
{#>
public void <#= cf.Name #> (<#= pars #>)
{
<#= classFullName + "." + cf.Name #>(<#= args #>);
}
<# }
else{ #>
public <#= cf.Type.AsFullName #> <#= cf.Name #>(<#= pars #>)
{
return <#= classFullName + "." + cf.Name #>(<#= args #>);
}
<#}
} #>
}
<#
} #>
}