eXtensible C# (XC#)
brings the idea of extensibility to a new level. XC# is both fully compatible with C# and extensible. In addition to all C# language features, XC# also supports arbitrary custom compile time attributes. This means that XC# allows you to go beyond the limited set of compile time attributes supported by C#. XC# comes with compile time attributes for obfuscation, code coverage, declarative assertions, and code verification at the same time that it understands your own compile time attribute. The rest of this article will focus on one of the XC# applications: declarative assertion attributes.
Imperative and Declarative Assertions
Remember imperative and declarative security? The same concept can be applied to assertions.
In a nutshell, assertions are Boolean expressions that should always be true. They are incredibly useful in specifying and debugging code.
However, one common mistake is to confuse assertions with exceptions. Exceptions model an abnormal termination, which can occur in correct code and be caught. On the other hand, assertions should never arise; an assertion violation indicates a bug.
Class Debug in the System.Diagnostics namespace provides many variants of the Assert method. Suppose I want to write the hash code of some parameter:
void WriteHashCode (object o)
Debug.Assert(o != null);
Note that I Assert that o is not null before getting the hash code. Without the call to Assert, passing null to WriteHashCode would result in a NullReferenceException.
XC# goes further by supporting declarative assertions:
[Requires ("o != null")]
void WriteHashCode2 (object o)
In this case, the XC# compiler adds a call to Assert at the beginning of the method. Because this technique can be fairly verbose, XC# comes with a short form for widespread assertions.
void WriteHashCode3 ([NotNull] object o)
Notice that in this case, the assertion applies to the parameter itself.
Besides being easier to write than imperative assertions, declarative assertions have two benefits.
First, declarative assertions are part of the metadata. A client could enumerate the assertions of a given method and do something interesting with them.
Second, declarative assertions are inherited. Suppose for example that the WriteHashCode method implements an interface method. In this case, it is more judicious to declare the assertion at the interface level.
void WriteHashCode ([NotNull] object o);
Because the interface method does not have any code, no call to Assert is generated at this point. However, the implementer does not need to declare the assertion, the compiler will do it automatically.
Flexibility and Extensibility
class ConsoleHashCodeWriter: IHashCodeWriter
public void WriteHashCode (object o)
// Call to Debug.Assert automatically generated here
Custom attributes provide a powerful way to communicate something about your component. Custom attributes can be user attributes, runtime attributes, or compile time attributes depending on the element of the system they interact with. The main point about attributes is extensibility. Not only can you apply attributes to your code but you can also define your own attribute with your own specific semantic. For example, some use attributes to specify tests. In fact, the limit is your imagination.