3

I'm trying to create an extension method using CodeDOM. There doesn't seem to be any support for them and using ExtensionAttribute (which C# uses internally to mark extension methods) is not allowed.

It's possible to use a trick to specify the this modifier, but how do I make the containing class static, so that the code actually compiles?

Since static is a C# concept, it's not exposed through the CodeDOM API. And setting TypeAttributes to TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.Public doesn't work, because

an abstract class cannot be sealed or static

How do I make the extension method to compile?

Community
  • 1
  • 1
svick
  • 236,525
  • 50
  • 385
  • 514
  • 1
    Future readers: I think the solid solution is now [tag:Roslyn], and adding a static modifier to the class. – user7116 Apr 12 '13 at 16:20

4 Answers4

7

I'm pretty sure you're looking for:

var staticClass = new CodeTypeDeclaration("Extensions")
    {
        Attributes = MemberAttributes.Public|MemberAttributes.Static
    };

However, this appears not to work. Interestingly enough:

provider.Supports(GeneratorSupport.StaticConstructors);
// True

provider.Supports(GeneratorSupport.PublicStaticMembers);
// True

But yet when you go and output it, no changes even though the Attributes property clearly changes from 0x00005002 to 0x00006003.

Per Microsoft Connect this is not possible:

Thanks for reporting this. Unfortunately, it doesn't look like we can support static classes for CodeDom.

The reason is that one of the design goals of CodeDom is to be language-independent, so that any code generated for one language can easily be generated for a different language. While static classes are used often in C#, VB does not support them. Therefore, adding support for static classes will mean that some code that can compile for C# won't be compilable for VB, which goes against our goals.

While we can't act on this issue, we ask that you please continue to provide feedback in the future to help us improve.


A dirty workaround:

var type = new CodeTypeDeclaration("Extensions");
type.Attributes = MemberAttributes.Public;
type.StartDirectives.Add(
    new CodeRegionDirective(CodeRegionMode.Start, "\nstatic"));
type.EndDirectives.Add(
    new CodeRegionDirective(CodeRegionMode.End, String.Empty));

Produces:

#region
static
public class Extensions
{
}
#endregion

Which compiles.

Community
  • 1
  • 1
user7116
  • 63,008
  • 17
  • 141
  • 172
  • That doesn't seem to do anything. And using `MemberAttributes.Private` doesn't change the class to private either. – svick Jun 10 '11 at 15:22
  • @svick: weird, I've confirmed this is the case with 4.0. Checking with older versions of the compiler. – user7116 Jun 10 '11 at 15:41
  • @svick: Updated to include a workaround using `CodeRegionDirective`s. – user7116 Jun 10 '11 at 16:32
  • I think I like my workaround better. If I have two bad solutions and one of them is “a clever trick”, I'd rather go with the more obvious one. – svick Jun 10 '11 at 17:02
  • @svick: agreed. Although you could always place my trick in an extension method (oh the irony) to make it more visible. Either way, what an aggravating find. – user7116 Jun 10 '11 at 17:05
  • 1
    This hack breaks down when attributes have to be placed on the static class. In my case, I had to disable generation of GeneratedCodeAttribute. Thanks for a nice hack though! – GregC Dec 01 '11 at 17:25
2

Instead of compiling the CodeCompileUnit directly, you could get the source code, replace class Extensions with static class Extensions and compile that code.

svick
  • 236,525
  • 50
  • 385
  • 514
  • Note that I don't like this solution and that I am still looking for a better one. – svick Jun 10 '11 at 15:32
  • I've updated my answer to include why you'll have to go this route. I've included the MS Connect article detailing the same. – user7116 Jun 10 '11 at 16:14
  • I wonder how will insert that into a namespace as a type since it's a `CodeSnippetTypeMember` – AymenDaoudi Jun 17 '21 at 05:44
2

Cleaned up the hack provided by sixlettervariables a little: placed it into a static method, as mentioned in the discussion.

public static void MarkAsStaticClassWithExtensionMethods(this CodeTypeDeclaration class_)
{
   class_.Attributes = MemberAttributes.Public;

   class_.StartDirectives.Add(new CodeRegionDirective(
           CodeRegionMode.Start, Environment.NewLine + "\tstatic"));

   class_.EndDirectives.Add(new CodeRegionDirective(
           CodeRegionMode.End, string.Empty));
}
GregC
  • 7,737
  • 2
  • 53
  • 67
0

You can get your code to compile exactly how your want it by converting it directly into a string, and then hacking it:

    private static CodeSnippetTypeMember CreateStaticClass(CodeTypeDeclaration type)
    {
        var provider = CodeDomProvider.CreateProvider("CSharp");
        using (var sourceWriter = new StringWriter())
        using (var tabbedWriter = new IndentedTextWriter(sourceWriter, "\t"))
        {
            tabbedWriter.Indent = 2;
            provider.GenerateCodeFromType(type, tabbedWriter, new CodeGeneratorOptions()
            {
                BracingStyle = "C",
                IndentString = "\t"
            });
            return new CodeSnippetTypeMember("\t\t" + sourceWriter.ToString().Replace("public class", "public static class"));
        }
    }
Daryl
  • 18,592
  • 9
  • 78
  • 145
  • Yeah, that's pretty much what I suggested in [my own answer](http://stackoverflow.com/a/6308622/41071). – svick Apr 10 '17 at 17:25