【问题标题】:Is it possible to test for an interface implementation using EnvDTE.ClassInfo objects是否可以使用 EnvDTE.ClassInfo 对象测试接口实现
【发布时间】:2013-04-16 21:26:54
【问题描述】:

全部,

我有一个 T4 模板,它生成样板代码,用于处理我的属性更改通知并根据我分配给类的属性自动为我注册依赖属性。我使用 EnvDTE 在项目中上下移动并检索 ClassInfo 对象的 IEnumerable 来完成此操作。然后,我枚举 ClassInfo.Attributes 以检索具有我创建的某些自定义属性(即 INotifyPropertyChangedAttributeAttribute:System.Attribute)的 ClassInfo 对象,其中包含我需要让模板为我编写样板代码的所有相关信息。

现在,我的问题是,是否可以(使用 EnvDTE)检查可能从基类继承的接口实现(例如 INotifyPropertyChanged),这样我的类中就不会出现两个 PropertyChanged 事件(一个在继承类中,一个在代码生成的部分类中)?

例如:

public class vmBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null) PropertyChanged(this, e);
    }
}

[INotifyPropertyChangedAttribute(Test1, typeof(string))] //NOTE: By including this attribute, T4 template will automatically generate properties.  What I need to know, though, is if the EnvDTE.ClassInfo can show Internface implementations as well so that I don't recreate the INotifyPropertyChanged Event
public partial class vm: vmBase //Implements INotifyPropertyChanged
{
    //....
}

[INotifyPropertyChangedAttribute(Test2, typeof(string))]
public partial class SomeClassThatDoesNotImplementInotifyPropertyChangedAlready
{
    //....
}

希望这有点道理。

有关使用 envDTE 和 T4 处理依赖属性注册的示例,请参阅 http://www.scottlogic.co.uk/blog/colin/2009/08/declarative-dependency-property-definition-with-t4-dte/。我的项目中的概念是相同的,只是我正在对其进行调整以处理 INotifyPropertyChanged 样板代码。

提前致谢。

【问题讨论】:

    标签: wpf silverlight t4 envdte boilerplate


    【解决方案1】:

    另一种方法(可能更健壮)是利用CodeClass 接口的IsDerivedFrom 属性。例如,您的模板可能如下所示:

    <#@ template  debug="true" hostSpecific="true" #>
    <#@ output extension=".cs" #>
    <#@ Assembly Name="System.Core" #>
    <#@ assembly name="EnvDte" #>
    <#@ import namespace="System" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Diagnostics" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Collections" #>
    <#@ import namespace="System.Collections.Generic" #> 
    <#    
      foreach(var ns in GetNamespaceElements())
      {
        foreach(var cc in ns.Members.OfType<EnvDTE.CodeClass>())
        {
          if (cc.IsDerivedFrom["System.ComponentModel.INotifyPropertyChanged"]) {
    #>YOUR_CODE_HERE<#  
          }
        }
      }
    #>
    
    <#+
      public IEnumerable<EnvDTE.CodeNamespace> GetNamespaceElements()
      {
        var visualStudio = (this.Host as IServiceProvider).GetService(typeof(EnvDTE.DTE))
                            as EnvDTE.DTE;
        var project = visualStudio.Solution.FindProjectItem(this.Host.TemplateFile)
                      .ContainingProject as EnvDTE.Project;
    
        var projItems = new List<EnvDTE.ProjectItem>();
        FillProjectItems(project.ProjectItems, projItems);
        var names = new HashSet<string>(projItems
          .Where(i => i.FileCodeModel != null)
          .SelectMany(i => i.FileCodeModel.CodeElements.OfType<EnvDTE.CodeElement>())
          .Where(e => e.Kind == EnvDTE.vsCMElement.vsCMElementNamespace)
          .Select(e => e.FullName));
    
        var codeNs = new List<EnvDTE.CodeNamespace>();
        FillCodeNamespaces(project.CodeModel.CodeElements.OfType<EnvDTE.CodeNamespace>(), codeNs);
    
        return codeNs.Where(ns => names.Contains(ns.FullName));
      }
    
      public void FillCodeNamespaces(IEnumerable<EnvDTE.CodeNamespace> parents, List<EnvDTE.CodeNamespace> all)
      {
        foreach (var parent in parents)
        {
          all.Add(parent);
          FillCodeNamespaces(parent.Members.OfType<EnvDTE.CodeNamespace>(), all);
        }
      }
    
      public void FillProjectItems(EnvDTE.ProjectItems items, List<EnvDTE.ProjectItem> ret)
      {
        if (items == null) return;
        foreach(EnvDTE.ProjectItem item in items)
        {
          ret.Add(item);
          FillProjectItems(item.ProjectItems, ret);
        }
      }
    #>
    

    【讨论】:

      【解决方案2】:

      我花了一点时间,但是是的 - 有一种方法可以通过 EnvDTE 找出给定的类是否以某种方式继承了给定的接口。

      此代码片段仅检测直接从另一个实现 INotifyPropertyChanged 的​​类继承的类。所以在使用它之前,可以在这里添加一些递归逻辑......

      <#
      // get a reference to the project of this t4 template
      var project = VisualStudioHelper.CurrentProject;
      // get all class items from the code model
      var allClasses = VisualStudioHelper.GetAllCodeElementsOfType(project.CodeModel.CodeElements, EnvDTE.vsCMElement.vsCMElementClass, false);
      
      // iterate all classes
      foreach(EnvDTE.CodeClass codeClass in allClasses)
      {
          // get all interfaces implemented by this class
      
          var allInterfaces = VisualStudioHelper.GetAllCodeElementsOfType(codeClass.ImplementedInterfaces, EnvDTE.vsCMElement.vsCMElementInterface, true);
          if (allInterfaces.OfType<EnvDTE.CodeInterface>()
                           .Any(i => i.Name == "INotifyPropertyChanged"))
          {
              #>Implements Interface Directly: <#= codeClass.FullName #>
              <#
      
              // find classes that derive from this code class
              foreach(EnvDTE.CodeClass potentialDerivingClass in allClasses)
              {
                  IEnumerable<string> theBases = VisualStudioHelper.GetAllCodeElementsOfType(potentialDerivingClass.Bases, EnvDTE.vsCMElement.vsCMElementClass, true).OfType<EnvDTE.CodeClass>().Select(cc => cc.FullName);
                  if (theBases.Any(b => b == codeClass.FullName))
                  {
                      #>Derives from implementing class: <#= potentialDerivingClass.FullName #>
                      <#
                  }
              }
          }
      }
      #>
      

      因此,假设一个实现 INotifyProperty 的类 A 发生了变化,一个从 A 派生的 B 类和另一个从 B 派生的 C 类,这段代码将提出实现 INotifyPropertyChanged 的​​类 A 和 B。

      注意:因为使用 EnvDTE 类不是很好,所以我使用了一个来自有形 T4 编辑器的免费模板库的可重用模板,名为“tangible Visual Studio Automation Helper” - 这使它更易于使用!

      【讨论】:

      • 好的。愚蠢的问题,但 VisualStudioHelper 到底是什么。是有形的吗?我正在使用有形 T4,但我从 Colin Eberhardt 借用了代码(请参阅相关链接)以通过 EnvDTE 进行枚举。
      • 我应该注意到,当我查看图库中的有形 Visual Studio 自动化助手时,我看到的是 DTEHelper 类,而不是 VisualStudioHelper 类。 DTEHelper 没有 GetAllCodeElementsOfType 函数。
      • 好的。找到了。显然,有一个“Visual Studio Automation Helper”和一个“Tangible Visual Studio Automation Helper”。我看的是前者。好的,我会试一试,看看我想出了什么。如果它有效,那么我会将其标记为答案。
      • 好的,现在是另一个问题。这是否还会捕获我在基类中实现从 InterfaceB 继承的 InterfaceA 的实例?即public interface InterfaceA:INotifyPropertyChanged, SomeOtherInterface { } public class vmBase : InterfaceA { ... } public class vm : vmBase { ... }
      • 还在有形 Visual Studio 自动化助手中发现了一个错误。尝试多次转换模板,看看从自定义属性生成的部分类会发生什么情况。我将不得不深入研究模板,看看我是否可以识别错误以及如何修复它。话虽如此,我还是 EnvDTE 的新手,因此请向任何可以向我展示具体哪里出了问题以及如何解决问题的人投了赞成票。
      【解决方案3】:

      可能有更好的方法,但我决定做的是创建一个新属性

      [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
      public class INotifyPropertyChangedInheretedAttributeAttribute : Attribute
      {
          public INotifyPropertyChangedInheretedAttributeAttribute():base()
          {
          }
      }
      

      这样,我可以在不从根本上改变我的 T4 模板的情况下测试该属性是否存在。不过,如果有更好的方法,我会很乐意将其标记为答案。

      【讨论】:

        【解决方案4】:

        作为所有解决方案的补充(我回答是因为我还不能添加评论:()。我用过

         foreach (CodeInterface iface in cc.ImplementedInterfaces)
         {
             if(iface.Name == "INotifyPropertyChanged")
             {
                   MessageBox.Show(cc.Name);
             }
          }
        

        此代码用于查找已实现的类。

        【讨论】:

          猜你喜欢
          • 2010-10-20
          • 2010-11-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-07
          • 1970-01-01
          • 2012-03-11
          相关资源
          最近更新 更多