How to get enum type in a T4 template
Asked Answered
V

2

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.

Viscose answered 22/8, 2016 at 11:36 Comment(19)
Why do you want to cast the enum to the correct value type?Wayfaring
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.Richmal
@OndrejTucny "In the process of writing a T4 text template"Denumerable
I'm not super familiar with T4s, but could you maybe use reflection on the Enum's type to know its inheritance?Denumerable
You might be looking for this: msdn.microsoft.com/en-us/library/… However, your question is ambiguous. T4 / CodeDom / Reflection? These are completely different concepts.Richmal
@Wayfaring I've added the reason to the problem description.Viscose
@OndrejTucny I'm using EnvDTE (CodeDom?) in T4 as I don't know how to read the code inside T4 without EnvDTEViscose
@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.Viscose
@OndrejTucny my question is not about Reflection. If you refer to EnvDTE as CodeDom then T4 and CodeDom is mixed in this case.Viscose
@OndrejTucny It appears the asker has an instance of the EnvDTE.CodeEnum type. So he asks how to get the underlying integer type from that, I think.Koons
@JeppeStigNielsen correctViscose
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.Koons
@JeppeStigNielsen This is the nearest three levels of bases outputted: System.Enum -> System.ValueType -> System.ObjectViscose
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 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.Koons
@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.Viscose
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?Koons
Let us continue this discussion in chat.Viscose
Can you "cheat" and use reflection anyway, with: Type.GetType(codeEnum.FullName).GetEnumUnderlyingType()?Koons
@JeppeStigNielsen that might be possible but I would have to rely on the assembly have been compiled once after each change.. not optimalViscose
L
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.

Leviticus answered 25/8, 2016 at 14:48 Comment(1)
Fantastic! I've changed the last .* to \w* to avoid whitespaces at the end of the group value. @"\benum .*\s*\:\s*(?<underlyingType>\w*)". Also I didn't know the \b, that's very smart. ThanksViscose
S
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 ;)

Scorpius answered 25/8, 2016 at 14:39 Comment(2)
This might work, but the OP is looking for a solution without reflection as this would require the source code to already be compiled.Leviticus
@DirkVollmar correct. I'm not looking for a Reflection based solutionViscose

© 2022 - 2024 — McMap. All rights reserved.