Potential improvements around Custom Attributes

Topics: C# Language Design
Apr 4, 2014 at 10:42 PM
Edited Apr 4, 2014 at 11:41 PM
Custom attributes are very similar to modifiers (public, sealed, virtual etc.) in terms of location (they are typically next to each other in code) but are more verbose when consumed.

I propose a language syntax sugar that lets you define a file-level or assembly-level synonyms for attributes. It will decrease the amount of lines of code and could improve readability. This is especially good for sets of custom attributes that are meant to "extend" the language.

Please find an example below. Web API was chosen because it is well-known, but it is not necessarily the very best use case. Try imagining a large class where each member is decorated with very similar or exact custom attributes.

Before:
public class OrdersController : ApiController
{
    [Route("api/orders")]
    [HttpGet]
    public IEnumerable<Order> GetOrders() { ... }

    [Route("api/orders"), HttpPost]
    public HttpResponseMessage CreateOrder(Order order) { ... }

    // WebDAV method
    [Route("api/orders")]
    [AcceptVerbs("MKCOL")]
    public void MakeCollection() { ... }
}
After:
using @routeorders = [Route("api/orders")];
using @httpget = [HttpGet];
using @httppost = [HttpPost];
using @mkcol = [AcceptVerbs("MKCOL")];

public class OrdersController : ApiController
{
    public @httpget @routeorders IEnumerable<Order> GetOrders() { ... }
    public @httppost @routeorders HttpResponseMessage CreateOrder(Order order) { ... }
    public @mkcol @routeorders void MakeCollection() { }
}
Further improvements not shown in example may include creating synonyms for several attributes at once and a way of sharing synonyms across different files and assemblies.
Apr 5, 2014 at 12:09 PM
This could be further extended to other language elements like keywords:
using @xprop = [XmlElement] public string { get; set; }

public class Student
{
    @xprop Name;
    @xprop Address;
}
Apr 7, 2014 at 1:32 AM
Web API already provides conventions for method names to specify HTTP method (Get*, Post*, ...) - check here http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api.

Attribute routing let you define common route at the class level - http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#prefixes.
Apr 7, 2014 at 4:34 AM
Edited Apr 7, 2014 at 5:19 AM
Web API already provides conventions for method names to specify HTTP method (Get*, Post*, ...)
While this is true for this case, it is certainly not true in all cases, for instance data serialization:
using @data = [DataMember];

[DataContract]
class Person
{
    @data public FullName { get; set; }
    public FirstName { get; set; }
    public LastName { get; set; }
}
However this is something that would be solved with a simple macro system. Perhaps C# should adopt compile time macros?
Apr 7, 2014 at 6:48 AM
Yep, macro was the first thing that came to my mind when I read this idea. But C# team ruled them out for a cause (can't remember exact cause though - may be it's not valid anymore).
Apr 7, 2014 at 8:07 AM
@Galich

Thank you for pointing out, I am aware just how much Web API is convention-driven. I did mention that this is not the best example, just a well-known API.
Apr 7, 2014 at 8:13 AM
@mirhagk, @Galich

I feel like macros is something that could easily be abused if too flexible. I dread imagining maintaining someone else's macro-heavy project. Unless of course automated refactoring for macros is top-notch. Custom attribute aliases on the other hand are very narrowly focused and in a way are similar to namespace aliases.
Apr 7, 2014 at 1:05 PM
Unless of course automated refactoring for macros is top-notch.
This is the thing. They'd have to be very careful about support for it, and make it a part of the language itself, rather than just a simple pre-processor. But there's quite a few ideas on here where someone really just wants the ability to define a macro.

Even the generics in C# are sometimes very constrained, and having macros would mean making a matrix type work with float int etc would be possible, just not supported at the assembly level.

Nemerle has macros in the language, it runs on .NET, and the macros in there are REALLY powerful. They allow you to even extend the language a bit, adding new keywords. The macro system is probably the sole reason a lot of the people that use it continue to use it, it's certainly what attracted me. C# could aspire to design something as powerful perhaps, however it'd be a SERIOUS overhaul of the parser.
Apr 7, 2014 at 1:55 PM
As much as I find macro ideas appealing, I don't want them to be part of C#. It's like Pandora's box, you open it once and it will be abused a lot.

The original idea is nice and saves some lines of code. But when you have more than one file using these macro? Can you easily spot a problem in 2nd file?
// OrdersController.cs
using @routeorders = [Route("api/orders")];
using @httpget = [HttpGet];
using @httppost = [HttpPost];
using @mkcol = [AcceptVerbs("MKCOL")];

public class OrdersController : ApiController
{
    public @httpget @routeorders IEnumerable<Order> GetOrders() { ... }
    public @httppost @routeorders HttpResponseMessage CreateOrder(Order order) { ... }
    public @mkcol @routeorders void MakeCollection() { }
}
// ProductsController.cs
using @productroute = [Route("api/products")];
using @httpget = [HttpPost];
using @httppost = [HttpGet];
using @mkcol = [AcceptVerbs("MKCOL")];

public class ProductsController : ApiController
{
    public @httpget @productroute IEnumerable<Product> GetProducts() { ... }
    public @httppost @productroute HttpResponseMessage CreateProduct product) { ... }
    public @mkcol @productroute void MakeCollection() { }
}
Apr 7, 2014 at 2:25 PM
Edited Apr 7, 2014 at 2:26 PM
I agree. Although there I was able to spot a difference, most of the time I wouldn't even look at the using statements.

A co-worker mentioned an interesting idea, although I have no idea how it may work. It'd be nice to have an attribute that can expand to an IEnumerable<Attribute> and have all of those attributes be applicable. I'd really have to dig into how attributes work to see if it would even work with the CLR though.

This attribute could be defined like any other, and would remove some of the pain with a lot of the repeated attributes.
Apr 7, 2014 at 2:50 PM
Edited Apr 7, 2014 at 2:54 PM
@Galich

I will be frank - it took me a couple of minutes to spot the problem. However I could argue that:
  1. It is possible to make this sort of mistake with other types of C# constructs (e.g. assigning a wrong value to a constant that is used throughout the solution).
  2. I personally strongly believe that custom attributes constraints should be extended to be more precise - I posted about this issue separately: Additional custom attributes 'constraints' . The Web API example use case is not very fortunate for me, but I can imagine it should be possible to do the following easily with Roslyn (you have to imagine that we are a part of Web API team):
[AttributeTargetMethodParameters(MethodParametersKind.SinglePoco)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class HttpPostAttribute : Attribute
{ ... }
It is already possible to do this with run-time validation and maybe with a custom Code Analysis rule (ex-FxCop) as well; Roslyn should make it possible to display a code-time error. This way it should be possible to reduce amount of mistakes not just with attribute aliases but with custom attributes in general.
Apr 7, 2014 at 5:52 PM
Frankly, I think the benefit is quite small. The attribute syntax used today is already quite clear (I think).

The cost of this idea is :
  • longer and longer declarations;
  • definition of what @mkcol means is far from where it's used.
I wouldn't use this feature even if it was available.

The goal of source code is not to be as short as possible, but to be clear, readable, with obvious intent and meaning. Concise, but not cryptic.
Apr 7, 2014 at 7:41 PM
Ok, I have received some constructive feedback - thank you guys. I tend to think that it might not be the best idea ever (although it would not look like the worst feature in the list of current suggestions - IMHO).