views:

424

answers:

7

Is it possible in C# to write MSIL code that will add a preprocessor directive to the code, e.g., #warning, if a certain condition is met? Or maybe this can be done with reflection, I don't know.

I'm trying to write a custom attribute that, if applied incorrectly to a class's method or property, will generate a compiler warning. Using the existing Obsolete attribute won't work because then just using my custom attribute causes the warning, and I don't want that. I want the custom attribute constructor to check for a condition, and if that condition is true then cause a compilation warning.

Update: after reading back over my question, I think what I'm asking for is impossible just because I'm mixing compile-time and runtime constraints. I think I'll end up going with a post-build task to check the just-built DLL and have it spit out error messages if the condition is true.

+1  A: 

You could probably do this with a post-build task that reflects your compiled code and checks for the condition. I don't have any experience creating MSBuild tasks but it's a place you can start.

Another suggestion once .NET 4.0 is released is to use Code Contracts to specify requirements for the parameters of the attribute's constructor. This would be caught at compile time.

Josh Einstein
+2  A: 

The compiler stores two things for custom attributes:

  • The attribute constructor to call
  • The data for each parameter to pass to the constructor

The constructor is only called when the application is running and someone calls GetCustomAttributes for your Assembyl, Type, MethodInfo, ParameterInfo, etc.

You have some other options to consider:

  • Write a custom MSBuild task that runs after the compilation stage, loads the compiled assembly and checks the application's attribute usage.
  • Use the AttributeUsage attribute to specify the code items to which the attribute can be applied.
  • Defer the attribute validation to runtime.
280Z28
A: 

I suspect the answer would be no, you can't, because the #warning directive is a CSC thing (i.e. you are directing the compiler to act in a certain fashion). When writing raw MISL, obviously, CSC doesn't come into mix, therefore there is no compiler to direct to do something.

Basically the a directive (indicate like '#warning' is an instruction to CSC to behave in a certain way under specified conditions.

Kevin Won
+2  A: 

In short, no. Pre-processing directives have no IL representation, since they only exist as metadata used during compilation of a source file.

The kind of thing you're doing might be better as a custom FxCop rule.

bobbymcr
+1  A: 

My gut feeling is no you cannot inject a #warning directive based on a custom attribute and condition since that is caught at compile time by the compiler, it is like a chicken and the egg situation as the custom attribute would have to be evaluated first before injecting a #warning but in order for that to happen a compile time action must be carried out first.

Hope this helps, Best regards, Tom.

tommieb75
+6  A: 

I saw this question coming from your previous thread. To mis-quote the great Jamie Zawinski: "Some people, when confronted with a problem, think "I know, I'll use an attribute." Now they have two problems".

An attribute is merely out-of-band data, compiled into an assembly's metadata. It cannot affect program execution or tool behavior, unless the program or the tool is explicitly programmed to recognize the specific attribute. It needs to do so using Reflection.

What you need to do is write your own tool. It should execute after an assembly is built, using the Post-Build step for a project. It needs to load the assembly and use Reflection to iterate the types in the assembly. For each type, iterate the methods with Type.GetMethods() and use MethodInfo.GetCustomAttributes() to discover and construct an attribute that might have been programmed.

You can use Type.GetInterfaces() to discover which interfaces are implemented by the type. You can now complain when you see that a method is present that implements an interface method but is missing an attribute that says so. And your ultimate goal: you can complain when you see a method with an attribute that says it implements an interface method but the type no longer inherits it.

Use Environment.ExitCode to make the tool fail the build if you see anything objectionable. This takes care of enforcement. Btw: programmers really hate to break the build. That might well encourage them to use the attribute religiously. Or it might encourage them to edit the post build step.

Hans Passant
Excellent answer, and the route that I'm going to end up following. I don't know if I should choose you as the selected answer to this question because it doesn't directly answer my MSIL question, but you're addressing the problem behind several of my recent questions!
Sarah Vessels
Are you saying he should use regular expressions instead? ;)
Remus Rusanu
@Remus - sounds like you know the quote. Sarah is probably a s/he. No, regex is not indicated here.
Hans Passant
+1  A: 

Have you ever heard of Boo? It has interesting ways in which you can hook up into the compiler pipeline. One such feature is called syntactic attributes, which are attributes that implement an interface that the compiler calls, so that they can participate in code generation.

class Person:
  [getter(FirstName)]
  _fname as string

  [getter(LastName)]
  _lname as string

  def constructor([required] fname, [required] lname):
    _fname = fname
    _lname = lname

The attributes in this code will generate public getters for the fields and null checks for the constructor parameters. It will all end up in the compiled assembly.

I've always wanted this kind of extensibility to be part of the C# compiler. Maybe it will one day. Until then, you can use a post-compiler, like CciSharp. CCiSharp will rewrite the CIL based on special attributes in the assembly, just like with Boo synctatic attributes.

Given this code:

class Foo {
  [Lazy]
  public int Value { 
    get { return Environment.Ticks; } 
  }
}

CCiSharp will mutate the code based on the LazyAttribute to this:

class Foo {
  int Value$Value; // compiler generated
  int Value$Initialized;
  int GetValueUncached() { 
    return Environment.Ticks;
  }
  public int Value  {
    get {
      if(!this.Value$Initialized) {
        this.Value$Value = this.GetValueUncached();
        this.Value$Initialized = true;
      }
      return this.Value$Value;
    }
}

CCiSharp is based on the Common Compiler Infrastructure project, the same used to implement the code contracts post compiler in the upcoming .NET Framework 4.0.

So this is how you can mutate the generated CIL.

But, a #warning directive does not have a CIL representation, it's a compiler directive only. To add this directive, what you must mutate is not the generated CIL, but the C# code itself. You'd have to implement a C# parser for that. I think the best option is, as stated in other responses, to create a post-build event that will reflect over the generated assembly and issue the desired warning.

Jordão