8

In the process of writing a T4 text template I ran into an issue I'm struggling to get by. I need to know the type of the enum I'm processing.

I have enums that are based on byte and ushort. I need the T4 text template to write code to cast the enum to the correct value type in order to serialize the enum and put it into a byte array.

This is an example enum of type byte

namespace CodeEnumType
{
    public enum MyEnum : byte
    {
        Member1 = 0,
        Member2 = 1,
    }
}

And this is my T4 text template

<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="EnvDte" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System.Collections.Generic" #>
<#
var serviceProvider = this.Host as IServiceProvider;
var dte = serviceProvider.GetService(typeof(DTE)) as DTE;
var project = dte.Solution.FindProjectItem(this.Host.TemplateFile).ContainingProject as Project;
var projectItems = GetProjectItemsRecursively(project.ProjectItems);
foreach(var projectItem in projectItems)
{
    var fileCodeModel = projectItem.FileCodeModel;
    if(fileCodeModel == null)
    {
        continue;
    }

    CodeElements codeElements = fileCodeModel.CodeElements;
    ProcessCodeElements(codeElements);
}
#>
<#+
public void ProcessCodeElements(CodeElements codeElements)
{
    if(codeElements == null)
    {
        return;
    }

    foreach(CodeElement codeElement in codeElements)
    {
        switch(codeElement.Kind)
        {
            case vsCMElement.vsCMElementNamespace:
                CodeNamespace codeNamespace = codeElement as CodeNamespace;
                CodeElements childCodeElements = codeNamespace.Members;
                ProcessCodeElements(childCodeElements);
                break;
            case vsCMElement.vsCMElementEnum:
                CodeEnum codeEnum = codeElement as CodeEnum;
                WriteLine(codeEnum.Name);
                //
                // here I would like the enum type
                //
                break;
        }
    }
}
public IEnumerable<ProjectItem> GetProjectItemsRecursively(ProjectItems items)
{
    if(items == null)
    {
        yield break;
    }

    foreach(ProjectItem item in items)
    {
        yield return item;

        var childItems = GetProjectItemsRecursively(item.ProjectItems);
        foreach(ProjectItem childItem in childItems)
        {
            yield return childItem;
        }
    }
}
#>

Notice the part where I wrote

        //
        // here I would like the enum type
        //

Here I have my enum information in the variable codeEnum and this is where my problem is. How do I get byte or ushort type from codeEnum?

I'm not using Reflection here as Type.GetType() does not work well if the assembly have not been compiled.

Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
Robin Theilade
  • 193
  • 1
  • 7
  • Why do you want to cast the enum to the correct value type? – qxg Aug 22 '16 at 11:42
  • 1
    What is `CodeEnum`? What code generation technique are you actually using — T4 or CodeDom? Seems like a bizzare mixture assuming `Code*` refer to CodeDom classes. – Ondrej Tucny Aug 22 '16 at 11:42
  • @OndrejTucny "In the process of writing a T4 text template" – Noémie Lord Aug 22 '16 at 11:48
  • I'm not super familiar with T4s, but could you maybe use reflection on the Enum's type to know its inheritance? – Noémie Lord Aug 22 '16 at 11:52
  • 1
    You might be looking for this: https://msdn.microsoft.com/en-us/library/system.enum.getunderlyingtype(v=vs.110).aspx However, your question is ambiguous. T4 / CodeDom / Reflection? These are completely different concepts. – Ondrej Tucny Aug 22 '16 at 12:06
  • @qxg I've added the reason to the problem description. – Robin Theilade Aug 22 '16 at 12:14
  • @OndrejTucny I'm using EnvDTE (CodeDom?) in T4 as I don't know how to read the code inside T4 without EnvDTE – Robin Theilade Aug 22 '16 at 12:20
  • @FrancisLord I could use Reflection but I don't know how I would get from a type of CodeEnum to System.Type of the same enum. – Robin Theilade Aug 22 '16 at 12:26
  • @OndrejTucny my question is not about Reflection. If you refer to EnvDTE as CodeDom then T4 and CodeDom is mixed in this case. – Robin Theilade Aug 22 '16 at 12:28
  • 1
    @OndrejTucny It appears the asker has an instance of [the `EnvDTE.CodeEnum` type](https://msdn.microsoft.com/en-us/library/envdte.codeenum.aspx). So he asks how to get the underlying integer type from that, I think. – Jeppe Stig Nielsen Aug 22 '16 at 12:58
  • @JeppeStigNielsen correct – Robin Theilade Aug 22 '16 at 13:06
  • What does `foreach (var b in codeEnum.Bases) { WriteLine(b); }` or similar give you? I suspect the "underlying type" is to be found through the `.Bases` property. – Jeppe Stig Nielsen Aug 22 '16 at 13:13
  • @JeppeStigNielsen This is the nearest three levels of bases outputted: System.Enum -> System.ValueType -> System.Object – Robin Theilade Aug 22 '16 at 13:23
  • Too bad; it is just like `.BaseType` during reflection then. Then reason why I had thought it might work was because [the `AddBase` method spec](https://msdn.microsoft.com/en-us/library/envdte.codeenum.addbase.aspx) said something like: _For `CodeEnum` objects, `Base` is a variant containing a fully qualified type name or `CodeType` object upon which the new enum is based. For C#, this is the underlying type of enum._ Not that I know very much about this. – Jeppe Stig Nielsen Aug 22 '16 at 14:52
  • @JeppeStigNielsen I hoped that as well. I've also looked at the implementation of `Type.GetEnumUnderlyingType()` and it looks at the type of the first enum member. I tried that before without any luck, but maybe I should try that again. – Robin Theilade Aug 22 '16 at 14:59
  • Interesting. The type has a ___non-static___ field which goes by the name `value__` and holds the underlying integer value. This is not the first of the "enumeration constants" defined by the type (these are static, and there may be zero enumeration constants). So what that method does is equivalent to (reflection): `.GetField("value__").FieldType` which actually works! I once wondered about this instance field in another question, _[Are .NET enum types actually mutable value types?](http://stackoverflow.com/questions/17891275/)_ – Jeppe Stig Nielsen Aug 22 '16 at 15:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121549/discussion-between-robin-theilade-and-jeppe-stig-nielsen). – Robin Theilade Aug 22 '16 at 15:33
  • Can you "cheat" and use reflection anyway, with: `Type.GetType(codeEnum.FullName).GetEnumUnderlyingType()`? – Jeppe Stig Nielsen Aug 22 '16 at 16:46
  • 1
    @JeppeStigNielsen that might be possible but I would have to rely on the assembly have been compiled once after each change.. not optimal – Robin Theilade Aug 22 '16 at 18:04

2 Answers2

8

The underlying enum type is not exposed by the EnvDTE object model. As a workaroud, you can retrieve the source code of the enum type definition and parse out the base type, e.g. using a regular expression:

foreach(CodeElement codeElement in codeElements)
{
    switch(codeElement.Kind)
    {
        case vsCMElement.vsCMElementNamespace:
            CodeNamespace codeNamespace = codeElement as CodeNamespace;
            CodeElements childCodeElements = codeNamespace.Members;
            ProcessCodeElements(childCodeElements);
            break;
        case vsCMElement.vsCMElementEnum:
            CodeEnum codeEnum = codeElement as CodeEnum;
            Write(codeEnum.Name);

            // get the source code of the enum
            string sourceCodeEnum = 
                codeEnum.StartPoint.CreateEditPoint().GetText(codeEnum.EndPoint);

            // a regular expression capturing the base type
            System.Text.RegularExpressions.Regex regex 
                = new System.Text.RegularExpressions.Regex(
                    @"\benum .*\s*\:\s*(?<underlyingType>\w*)");

            var match = regex.Match(sourceCodeEnum);
            if (match.Success)
            {
                WriteLine(" : " + match.Groups["underlyingType"].Value);
            }

            break;
    }
}

Note that the regex in the sample is just a very simple pattern that might need to be adjusted to cope with different source code formatting.

Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Fantastic! I've changed the last .* to \w* to avoid whitespaces at the end of the group value. @"\benum .*\s*\:\s*(?\w*)". Also I didn't know the \b, that's very smart. Thanks – Robin Theilade Aug 25 '16 at 19:18
2

This method should return what you are looking for.

static Type GetEnumType<T>() where T : struct, IComparable
{
    var ti = (TypeInfo)typeof(T);
    return ti.DeclaredFields.First().FieldType;
}

Hope this helps ;)

sQuir3l
  • 1,373
  • 7
  • 12