GetType () в шаблоне T4 всегда возвращает ноль - PullRequest
1 голос
/ 23 марта 2020

Я пытаюсь написать шаблон T4, который будет генерировать частичный файл класса для каждого класса модели в папке. Возможно, я делаю это неправильно, поэтому, пожалуйста, не стесняйтесь предлагать улучшения для битов, которые, кажется, работают.

В моем тестовом проекте есть папка Models, которая содержит пару простых классов, например, Person.cs содержит. ..

using System;

namespace WithTT.Models {
  public partial class Person {
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string Surname { get; set; }
    // etc...
  }
}

Мой шаблон T4 (в той же папке) использует вспомогательный шаблон MultipleOutputHelper.ttinclude . Файл T4 в настоящее время выглядит следующим образом ...

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ include file="MultipleOutputHelper.ttinclude" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".log" #>

<# var manager = Manager.Create(Host, GenerationEnvironment); #>
<# manager.StartHeader(false); #>
<# manager.EndBlock(); #>

<#
  string folder = this.Host.ResolvePath("."); // get current folder
  folder = folder.Substring(0, folder.Length - 2); // knock the "\." off the end
  // Loop through each file and create an AbcExt.cs partial class
  foreach(string file in Directory.GetFiles(folder, "*.cs").Where(f => !f.EndsWith("Ext.cs")).Select(f => f.Replace(".cs", ""))) {
    manager.StartNewFile($"{file}Ext.cs"); 
    string className = Path.GetFileNameWithoutExtension(file);
    string ns = File.ReadAllLines(file + ".cs").Single(l => l.StartsWith("namespace")).Replace("namespace", "").Replace("{", "").Trim();
#>
// This code was generated from a template, do not modify!
namespace <#=ns#> {
  public partial class <#=className#> {
    // TODO AYS - Write the With() method...
  }
}
<#
manager.EndBlock();
}
#>

<# manager.Process(true); #>

До сих пор это прекрасно работает и создает базовые c файлы кода, которые выглядят следующим образом

// This code was generated from a template, do not modify!
namespace WithTT.Models {
  public partial class Person {
    // TODO AYS - Write the With() method...
  }
}

Теперь некоторые из вспомогательных методов, которые мне нужно использовать для генерации кода, который я здесь хочу, требуется тип рассматриваемого класса. Завладев пространством имен (в переменной ns) и именем класса (в переменной className), я ожидал, что смогу сделать следующее ...

Type t = Assembly.GetExecutingAssembly().GetType(ns + "." + className);

.. .or ...

Type t = Type.GetType(ns + "." + className);

Однако, какой бы я ни использовал, t всегда равен нулю. Пространство имен и имя класса в порядке, как вы можете видеть из сгенерированного файла, и согласно двум верхним ответам на этот вопрос , любой из них должен работать.

Если я попробую тот же код в Program.cs тестового проекта я получаю тип без проблем.

Кто-нибудь знает, как я могу получить Type рассматриваемого класса? Спасибо

Редактировать Я понял, почему тип всегда нулевой. Если я выведу пространство имен сборки в вывод, скажем так:

// <#=Assembly.GetExecutingAssembly().FullName#>

... тогда я вижу TemporaryT4Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null, что явно не то же самое, что сборка, которая содержит классы. Теперь моя проблема - выяснить, как получить сборку проекта, а не сгенерированную из шаблона T4

1 Ответ

1 голос
/ 23 марта 2020

Оказалось довольно просто. Этот SO-ответ показал следующие две строки ...

var path = this.Host.ResolveAssemblyReference("$(TargetPath)");
var asm = Assembly.LoadFrom(path);

Как только я загрузил сборку, получить тип было довольно просто ...

Type t = asm.GetType(fileNamespace + "." + className);

РЕДАКТИРОВАНИЕ Я обнаружил главный недостаток этого подхода, который заключается в том, что Assembly.LoadFrom блокирует сборку, что означает, что вы не можете перестроить проект без перезапуска Visual Studio.

В альтернативном методе используется Assembly.Load(File.ReadAllBytes(path)), которая позволяет избежать этой проблемы (поскольку она создает Assembly из копии фактической сборки), но требует, чтобы вы знали путь и имя сборки.

Для моих целей следующее сделал трюк ...

  IServiceProvider hostServiceProvider = (IServiceProvider)Host;
  EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
  Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects;
  EnvDTE.Project dteProject = (EnvDTE.Project)activeSolutionProjects.GetValue(0);
  string defaultNamespace = dteProject.Properties.Item("DefaultNamespace").Value.ToString();
  string templateDir = Path.GetDirectoryName(Host.TemplateFile);
  string fullPath = dteProject.Properties.Item("FullPath").Value.ToString();
  fullPath = fullPath.EndsWith("\\") ? fullPath.Substring(0, fullPath.Length-1) : fullPath;
  string exePath = fullPath + "\\bin\\Debug\\" + defaultNamespace + ".exe";
  Assembly asm = Assembly.Load(File.ReadAllBytes(exePath));

... что довольно много кода (хотя может быть возможно очистить его, если вы понимаете это лучше, чем я сейчас). В обычных условиях Assembly.LoadFrom не рекомендуется, но здесь он работает нормально.

Основная проблема этого подхода заключается в том, что я предположил, что сборка находится в папке bin\Debug (не плохое предположение), назван так же, как проект (не очень хорошее предположение, но, вероятно, в целом верно) и представляет собой .exe (довольно ошибочное предположение, которое довольно часто ошибается). Последнее предположение, возможно, можно было бы смягчить, посмотрев в коде выше, чтобы найти тип проекта, но у меня не было времени, чтобы попробовать это. Я оставляю комментарий здесь как предупреждение для любого, кто копирует код.

...