[Home](../) › [Blog](../blog) › Thinking beyond Roslyn source generators and aspect-oriented programming

# Thinking beyond Roslyn source generators and aspect-oriented programming

> The blog post discusses Roslyn source generators and aspect-oriented programming (AOP), exploring their uses, limitations, and how they compare. The author argues that while source generators are useful, they don't replace AOP. The post also covers the philosophy of programming languages and their role in reducing cognitive load on developers.

Published: 2020-07-22 · Author: Gaël Fraiteur · Canonical: https://postsharp.net/blog/thinking-beyond-roslyn-source-generators-and-aspect-oriented-programming

<p>When I first heard about Roslyn source generators in the&nbsp;<a
        href="https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/" target="_blank">announcement
        post,</a>&nbsp;I first wanted to write a blog post comparing them to aspect-oriented programming. This blog post
    progressively turned into a philosophical essay. What is programming? What are programming languages? What is design
    itself? These questions are seldom asked, and philosophical essays are not a popular genre in our industry. Yet, I
    think they are extremely relevant.</p>

<p>In an industry where&nbsp;<em>hacking</em>&nbsp;is so celebrated, I would like today to offer the
    opposite:<em>&nbsp;a survey of the activity of building software from a humanist and cognitive point of view.</em>
</p>
<h2>What are Roslyn source generators?</h2>
<p>If you haven&rsquo;t read about source generators, then, in a nutshell, it&rsquo;s a new extensibility point of the
    Roslyn compiler that allows you (and any NuGet package) to dynamically emit new C# code and add it to the
    compilation in progress. Source generators receive an object model representing the code being compiled and, based
    on that, can emit new code. They cannot, however, modify existing code.</p>
<p>Strictly speaking, Roslyn source generators don&rsquo;t enable any scenario that was impossible before. The novelty
    with Roslyn source generators is that they are built into the Roslyn compiler, which gives two advantages. First,
    the build will be faster because the first stage of the compilation will need to be performed only once instead of
    twice. Second, since the C# compiler is hosted in the IDE, the code-behind artifacts will be visible at design time
    in the IDE using tools like IntelliSense.</p>
<p>Another decisive benefit of Roslyn source generators is that they are invented by Microsoft and will therefore
    benefit from immediate, broad, but also uncritical adoption. We can expect Roslyn source generators to be widely
    used and abused as soon as they become a stable part of .NET.</p>
<h2>When to use source generators?</h2>
<p>Microsoft gives a couple of use cases of source generators. Most of them are excellent. Spoiler alert: not all are.
</p>
<p>Among justified use cases of source generators, we can list:</p>
<ul>
    <li>Generation of code-behind files for XAML, ASP.NET, settings, &hellip; which is now being done as standalone
        MSBuild tasks.</li>
</ul>
<ul>
    <li>Creation of an &ldquo;application catalogue&rdquo; for instance to index ASP.NET controllers, MEF
        components&hellip; which is now typically performed at run-time during application start-up using
        System.Reflection. It could also have been implemented using a post-compilation tool like PostSharp, but there
        was no significant attempt to do it.</li>
</ul>
<ul>
    <li>Generation of infrastructure code that requires the implementation of methods such as&nbsp;<a
            href="https://github.com/postsharp/PostSharp.Community.ToString"
            target="_blank">ToString</a>&nbsp;or&nbsp;<a
            href="https://github.com/postsharp/PostSharp.Community.StructuralEquality" target="_blank">Equals</a>,
        GetHashCode, comparison operators&hellip; which is now typically implemented using tools like PostSharp.</li>
</ul>
<p>I believe that in these three classes of use cases, source generators are superior to the technology they replace.
</p>
<h2>When not to use source generators?</h2>
<p>Source code generators are by design&nbsp;<em>additive only</em>, i.e. you can add new files to the compilation
    (possibly new partial classes), but you cannot modify existing ones. If you&rsquo;re facing a problem that you would
    naturally address by editing existing code and that could not be isolated into a partial class, then a source
    generator is not the right job for the task.</p>
<p>To illustrate this rule, I will take an example directly from Microsoft&rsquo;s materials, and I will show why it
    could be considered as an abusive use of this technology.</p>
<p>Let&rsquo;s look at the code, reproduced from Microsoft&rsquo;s&nbsp;<a
        href="https://github.com/dotnet/roslyn-sdk/blob/master/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs"
        target="_blank">samples</a>:</p>
<pre class="brush: c-sharp; first-line: 1; tab-size: 4; toolbar: false; ">public partial class UserClass
{
    [AutoNotify]
    private bool _boolProp;
 

    [AutoNotify(PropertyName = "Count")]
    private int _intProp;
}</pre>
<p>&nbsp;</p>
<p>The source generator would generate the following partial class:</p>
<p>&nbsp;</p>
<pre class="brush: c-sharp; first-line: 1; tab-size: 4; toolbar: false; ">public partial class UserClass : INotifyPropertyChanged
{
    public bool BoolProp
    {
        get =&gt; _boolProp;
        set
        {
            _boolProp = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("UserBool"));
        }
    }
 
    public int Count
    {
        get =&gt; _intProp;
        set
        {
            _intProp = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}<br><br></pre>
<p>&nbsp;</p>
<p>What&rsquo;s wrong with that?</p>
<p>At first sight, the tool seems to do a good job at avoiding writing the boilerplate. From this angle, source
    generators look good.</p>
<p>It&rsquo;s true that this implementation does not support the advanced features proposed by PostSharp like computed
    properties or dependencies on child objects, but remember that this is only a simple example. Nothing in theory
    prevents you from performing a complex analysis of the code model and generate smarter code. If you put in work for
    months, you will eventually be able to implement that.</p>
<p>Another objection may be that you need to remember to assign the property instead of the underlying field, even if
    the field is what you see in your source code, otherwise the notification would not be raised. This remark could be
    addressed by emitting an error when you&rsquo;re trying to directly assign the field.</p>
<p>Therefore, from the point of view of what you&nbsp;<em>can</em>&nbsp;do with it, there&rsquo;s nothing wrong at
    source generators in this case of implementing INotifyPropertyChanged. You&nbsp;<em>can</em>&nbsp;do everything you
    need for this use case. But you&nbsp;<em>must</em>&nbsp;define fields instead of properties,
    and&nbsp;<em>must</em>&nbsp;remember to set the property and not the field, even if the field and not the property
    is visible of your source code.</p>
<p>The problem using a source generator to address the decade-old problem of INotifyPropertyChanged is that this
    solution is not&nbsp;<em>idiomatic</em>.</p>
<p>If you already have a class with properties and need to add INotifyPropertyChanged, you will have to refactor your
    properties into fields. Your code will look unnatural (breaking the &ldquo;principle of least surprise&rdquo;) and
    that refactoring itself is unproductive. You will have to add the right options to your [AutoNotify] so that it
    emits accessors with the visibility you need while this problem is already elegantly addressed by the C# language.
    These are all design smells that show that the solution is not idiomatic.</p>
<p>Implementing INotifyPropertyChanged is a textbook problem for aspect-oriented programming (AOP). Actually, we can say
    that [AutoNotify] is an aspect &ndash; but inelegantly implemented with a source generator.</p>
<p>Unlike source generators, AOP frameworks are specifically designed to add infrastructure concerns into hand-written
    code, without requiring you to change it. Well-written aspects work with your code, not against it. That is, good
    aspect-oriented frameworks are idiomatic to their host language.</p>
<p>Source generators are a useful tool, but they don&rsquo;t replace aspect-oriented programming.</p>
<h2>Microsoft&rsquo;s love-hate relationship with AOP</h2>
<p>Microsoft has had a long love-hate relationship with aspect-oriented programming. As all large corporations,
    Microsoft has the psychology of a dragon with many heads &ndash; each with its own eyes, mouth and brain. No wonder
    they are not always synchronized.</p>
<p>On one side, many teams have added AOP-ish features into their own product:</p>
<ul>
    <li>Microsoft Transaction Server, later renamed COM+</li>
    <li>Custom behaviors in WCF</li>
    <li>Interceptors in Unity</li>
    <li>Action Filters in ASP.NET MVC and ASP.NET MVC Core</li>
</ul>
<p>These teams didn&rsquo;t just add these features because they were fun, but because they were useful. In all these
    technology stacks, aspects can be applied only at the boundaries of a component because the technology brokers the
    call between the client and the component. Neither the compiler nor the runtime engine was involved there, only the
    service host.</p>
<p>Another example of technology that vitally needs aspect-oriented programming is WPF. The amount of code required to
    implement INotifyPropertyChanged, dependency properties or command is colossal. This is why INotifyPropertyChanged
    is cited as a use case of source generators. Because it&rsquo;s so useful.</p>
<p>Last, some Microsoft teams used PostSharp to improve their productivity.</p>
<p>It&rsquo;s not surprising: aspect-oriented programming is incredibly useful. Our telemetry data suggest that AOP can
    easily reduce the source code size and complexity by 15%. That means that some users of our product benefit from 1%
    and should probably stop using it, but other users decrease their code size by 50%. A customer of us confessed
    saving&nbsp;<em>millions</em>&nbsp;of dollars thanks to PostSharp. This is how big AOP is. Many PostSharp users say
    to us they want it to be a standard part of .NET. There are several millions of full time C# developers in the
    world. For every million developers, 150,000 are now writing code that could be generated with better quality by an
    algorithm. Just think about that huge waste of time.</p>
<p>The problem is that, on the other side, the C# team has really never embraced aspect-oriented programming. It is not
    even on their feature map.</p>
<p>Egoistically it&rsquo;s good for us that the language team does not care about AOP, because we can make some (hardly
    won) money by filling this gap. But for the community, this is a pity.</p>
<p>In the meantime, you can rely on PostSharp if you want to leverage the benefits of the AOP. Frankly, many companies
    are not willing to take the risk to rely on a small company such as ours for a critical part of their build process
    &ndash; even if we&rsquo;ve been there for almost 15 years. I&rsquo;m grateful for the thousands of customers who
    decided to trust us, but I also understand that, for many of them, the vendor risk is a higher obstacle than the
    license price.</p>
<p>Therefore, I believe Microsoft&rsquo;s love-hate relationship with AOP is detrimental to the productivity of the .NET
    ecosystem &ndash; especially to large projects.</p>
<p>Yes, aspect-oriented programming is an old concept and its hyped years are behind us. I&rsquo;m not a fan of the
    AspectJ approach and have never tried to copy it into .NET. But the fact is that the AOP addresses an abstraction
    gap of C# and that this gap itself cannot be ignored. AOP, AspectJ and even PostSharp are just solutions to a
    problem. The fact that you don&rsquo;t like the solution does not magically make the problem disappear.</p>
<p>So, how can we approach that problem? What problems are programming languages solving anyway?</p>
<h2>What is a programming language?</h2>
<p>A programming language is a language. A language used by humans and by humans only. Compilers and interpreters are
    tools that convert the elements of human speech into instructions that can be executed by a computer. But the
    language itself is designed for human consumption. Humanity is the most fundamental characteristic of a programming
    language.&nbsp; It seems paradoxical because programming languages have traditionally been designed by people with a
    technological background and not, say, in philosophy or cognitive psychology. It&rsquo;s maybe because languages
    used to be designed by the same group of people who implemented the compilers which is, definitively, a
    technological activity.</p>
<p>The larger the abstraction gap between the language and the hardware, the more complex the compiler or interpreter
    needs to be. During the first decades of computing, the complexity of compilers and interpreters was severely
    constrained by the limitations of hardware, especially CPU and RAM. In the 1970s, specialized hardware
    called&nbsp;<a href="https://en.wikipedia.org/wiki/Lisp_machine" target="_blank">LISP machines</a>&nbsp;were
    developed. There were even&nbsp;<a href="https://en.wikipedia.org/wiki/PicoJava" target="_blank">Java
        coprocessors</a>&nbsp;in the late 1990s. The C language, with its system of header files, was designed to
    optimize the memory usage of the compiler. In these pioneering times, programming languages had to remain close to
    the hardware &ndash; i.e. homomorphic to the machine.</p>
<p>At each generation, the languages became closer to the way humans reason. Object-oriented programming was invented in
    the 1960s to create a language that would allow for expressing models of the physical world. It became SIMULA. Take
    the time to think about the concepts of&nbsp;<em>class</em>&nbsp;and&nbsp;<em>inheritance</em>. These concepts are
    purely irrelevant to hardware. They are however of the upmost importance for human reasoning and have been first
    theorized, in the Western culture, by Aristotle in the 4th century BC. (Please don&rsquo;t make me say that
    Aristotle invented OOP!) We could say that programming languages became more and more&nbsp;<em>logomorphic</em>, if
    we accept logos in the meaning of reasoning (logic) and not speaking. Successful programming languages mimic the way
    we think.</p>
<p>However, in today&rsquo;s world, RAM and compilation time is no longer a constraint. The compiler only consumes a
    fraction of the total build time. What, then, really prevents programming languages from becoming even more
    logomorphic? Two things: the lack of investment in compilers (I believe every million dollars invested in the C#
    language can translate into a&nbsp;<em>billion</em>&nbsp;of productivity gains for the community), and a system of
    mental habits that internalized constraints that have now become obsolete.</p>
<h2>What problem do programming languages and frameworks solve?</h2>
<p>The problem developers solve is generally to&nbsp;<em>implement</em>&nbsp;a solution to a business problem &ndash;
    for instance implementing a web site. Implementation can be simple or complex, can require little or more design.
    Let&rsquo;s call the implementation problem the&nbsp;<em>first problem</em>. Developers typically focus their
    attention on the first problem. Developers are intellectual workers; their job is principally
    to&nbsp;<em>produce</em>, even they may also take many other roles on their job. The realm of developers is composed
    of source code, development tools, APIs, cloud services, and other software or hardware artifacts.</p>
<p>Development managers, however, have a different problem. Instead of focusing on the first problem, managers need to
    focus on the&nbsp;<em>activity</em>&nbsp;of developing, i.e. on the team of developers, on the way they cooperate,
    communicate or conflict, on hiring and firing, on the financing of the team and its relationships with external
    stakeholders, and so on. The realm of managers is composed of people, organizations and processes. Let&rsquo;s call
    that the focus on the social activity of software development the&nbsp;<em>second problem</em>.</p>
<p>In other words, the second problem is what gets revealed when you try to raise your awareness from the technical
    problem to the&nbsp;<em>activity</em>&nbsp;of solving the technical problem &ndash; an activity that is human and
    social. It&rsquo;s like mindfulness but applied to software development. A higher level of awareness.</p>
<p>Now, if you look at software development from this higher point of view, ask yourself this question: What are the
    main constraints the industry is facing? What are its principal challenges? Is it the lack of processing power, of
    RAM, of storage, of network connectivity, of energy or battery capacity? For most of us, no. I concede all these
    problems are real, but they are not the real limiting factor. It is not the&nbsp;<em>scarce economic
        factor</em>&nbsp;that our industry has to cope with.</p>
<p>The real challenge of our industry is the&nbsp;<em>scarcity of cognitive resources</em>. What really limits
    development teams and companies is the amount of intelligence that they can attract to their projects with their
    budget. Everybody&rsquo;s cognitive capacity is finite. Our individual cognitive capacity determines the maximal
    complexity of the problem we are capable to &ldquo;load&rdquo; into our brain and therefore reason about. To cope
    with this finite capacity, we can attempt to decompose big problems into several smaller problems that can be
    reasoned about separately (I will see later how it affects the design of programming languages), but individual
    abilities put a limit on the size of these atomic problems. When the problem we are considering is well within our
    abilities, we are quick and swift at the task. When it gets closer our limits, our productivity decreases sharply.
    When complexity excesses our ability, we start simplifying the problem, and as our simplifications become incorrect,
    we start designing incorrect solutions. The most complex atomic problem a team is able to solve is limited by the
    capacity of its most intelligent member. The first dimension of the scarcity of cognitive resources is our
    individual finiteness.</p>
<p>The second dimension is obvious: as a company, the number of people you can hire is limited by your budget. The more
    brains, the more atomic subproblems you can solve in parallel &ndash; supposing you can split your problems in
    enough independent subproblems that are simple enough to be handled by average team members. This ability to split
    problems and make social cooperation efficient is in itself a serious challenge that our industry addresses with
    tools and practices in broad areas: versioned source control, project management, specialization of teams in the
    company and companies in the industry, analyzing and modeling, and so on. Software development is a human activity;
    besides its cognitive aspects (working with individual brains), it also has social/political dimensions (organizing
    the efficient cooperation of many individuals).</p>
<p>Let&rsquo;s now go back to programming languages and development tools.</p>
<p>If the real challenge of our industry is to deal with the scarcity of cognitive resources in both its individual and
    collective dimensions, it becomes clear that programming languages should be primarily designed with this challenge
    in mind. Programming languages should reduce the cognitive load on developers, so developers can do more, and more
    people can be developers. Programming languages should allow for the higher problem decomposition, so atomic
    problems become simpler and more individuals can simultaneously cooperate. Finally, programming languages should
    match the level and mechanisms of abstraction of human reasoning, i.e. should reduce the impedance between human
    thought and source code.</p>
<p>The best programming language is not necessarily the one that allows to build a given functionality with the smallest
    number of characters. It is the one that requires the lowest cognitive resources, or the one that allows to best
    distribute the work across a team of various qualifications.</p>
<h2>Patterns and language extensions</h2>
<p>Looking at programming as a work with language opens interesting insights, ones you don&rsquo;t get when you&rsquo;re
    stuck at the implementation level. When we change our point of view, when we focus our awareness on something
    different, we naturally come to different thoughts. On the contrary, when we always stick to implementation details,
    we&rsquo;re always in micro-optimization mode and we cannot make conceptual leaps.</p>
<p>We all know the famous Gang of Four&rsquo;s book&nbsp;<em>Design Patterns: Elements of Reusable Object-Oriented
        Software</em>, but only a few developers have gone back to the sources and studied the works of the spiritual
    father of the design pattern theory: Christopher Alexander, born in 1936, an influential architect and design
    theorist. He surveyed the theoretical foundations of the activity of designing artifacts such as barns or kettles
    (design as the activity of designing and not the result of this activity). The contribution of the Gang of Four was
    to apply these principles to software engineering &ndash; with the huge success we know. It is regrettable that most
    software developers now look at this work as a cookbook, now largely obsolete, and skip the theoretical parts. The
    theoretical foundations of design patterns are still of immense relevance for the design of programming languages
    and frameworks.</p>
<p>Alexander draws our attention to the fact that every profession has recurring elements of design that he calls
    patterns. A part of the education of every student of every trade is to study these patterns. For
    instance,&nbsp;<em>Carpentry for Boys</em>&nbsp;published in 1914 lists more than a hundred of carpentry patterns
    that all apprentices should learn. Not only should they learn to execute them, but they should also understand in
    which context they should be used. Patterns are reusable elements of solution to recurrent but non-identical
    problems.</p>
<p>An essential characteristic of patterns is that they are&nbsp;<em>named</em>. Because of that, they form a
    professional dialect on the top of the common language. Patterns are not only elements of design, but also elements
    of language. Because we&rsquo;re able to talk about them, we&rsquo;re also able to reason about them.</p>
<p>When carpenters talk of a rabbet join, they don&rsquo;t need to focus their intelligence on the specific cuts and
    angles. Their reasoning works at the level of abstraction of the pattern. When we are reasoning in terms of patterns
    instead of their implementation details, we are working with fewer but larger elements, and therefore our brain,
    with the same limited capacity, can handle more complex problems, and recursively, in a virtuous spiral that
    produces a richer and richer collection of reusable solutions.</p>
<p>The development of a trade is therefore always concomitant with the enrichment of its dialect.</p>
<p>How does that translate to programming languages? How can we extend our common language &ndash; say C# &ndash; so it
    reflects the expertise we have built in a domain?&nbsp;&nbsp;</p>
<p>First, we are using the tools object-oriented programming gives us. We have named classes to represent concepts,
    named methods to represents actions on these concepts, and named properties to represent observable characteristics
    of these concepts. That seems sensible and it is. But it is not sufficient to represent the whole realm of our
    reasoning.</p>
<p>How do you represent a pattern in C#? Well, you don&rsquo;t. The C# programming language does not allow you express,
    in an executable way, such a simple thing as the statement &ldquo;the class Foo is thread-affine&rdquo; or
    &ldquo;the class Bar is observable&rdquo;, even if thread affinity and observability are two well-identified
    patterns of UI development.</p>
<p>There is no way to extend the C# language with concepts of thread affinity, observability or other patterns that may
    be relevant to a specific niche, industry, or project. Either the pattern is implemented in the language (like
    event, lock, using, async, enumerators), or it is not. You cannot extend the language yourself.</p>
<p>I see this limitation as an important shortcoming of C#. It is essential that the programming language matches the
    level of abstraction at which people are reasoning.</p>
<p>There is now a gap between the way developers&nbsp;<em>think</em>&nbsp;and the way they&nbsp;<em>write code</em>. I
    think it&rsquo;s crucial that the software development tools industry does more to bridge it.</p>
<p>This is what we have been doing with PostSharp since 2004 &ndash; although I acknowledge it took me 10 years to
    understand what my job was really about. In the same gesture, we seal a brick, make a wall, build a cathedral, and
    perfect the art of building cathedrals. It&rsquo;s important to be experts in sealing bricks, but we must not lose
    the focus on the edifice we&rsquo;re shaping and the perfection of our trade.</p>
<p>We must not forget that programming today is only accessorily about doing something that a machine can execute, and
    principally about coping with the limitations of your own mental capacities.</p>
<p>When designing programming languages and frameworks, we must not forget about the cognitive dimension of our work.
</p>
<h2>Orthogonal problem decomposition</h2>
<p>Our brain can only handle a limited number of elements at a time. When we must reason about a problem of hundreds of
    moving parts, we cannot reason about all parts at the same time. One approach is called divide-and-conquer. We
    recursively divide the problem into subproblems and implement them as isolated artifacts &ndash; organized in a
    beautiful tree that resembles a botanic taxonomy: projects, namespaces, classes and finally methods. Besides this
    source code tree, we have a second hierarchy: inheritance., i.e. abstraction of common behaviors. These two
    hierarchies are how object-oriented programming implement divide-and-conquer.</p>
<p>There&rsquo;s another technique humans use when designing complex systems: decomposition into several facets that are
    as independent (orthogonal) as possible. For instance, when an architect designs a house, she does not need to
    design the thermal and electrical solutions at the same time. These concerns can be addressed at different times,
    drawn on different layers in the CAD editor. Later on in the project, the interactions between these layers will be
    validated. In the meantime, thanks to this decomposition, her brain could focus on different subsets of the problem.
</p>
<p>Software has also different facets that are partially independent from each other. For instance, security and
    robustness are two different facets that are almost independent from each other and from the business functionality.
    However, traditional object-oriented programming, with its separation of concerns into methods, classes, namespaces
    and projects, does not allow us to express these different facets in different &ldquo;layers&rdquo; of the code. All
    facets need to be merged together in the same classes and methods implementing the business features.</p>
<p>As you may have guessed, aspect-oriented programming was designed to address this shortcoming.</p>
<p>Aspect-oriented programming was not theorized to address the problem of design patterns, probably because both AOP
    and patterns were theorized at the same time and independently. It is much more powerful to theorize and implement
    AOP as a tool to build implementation of patterns.</p>
<p>You may not like AOP in the way it was designed almost 20 years ago, with its system of join points and pointcuts.
    You may not like AspectJ, its canonical implementation. Honestly, I also don&rsquo;t like them. You may not like
    PostSharp, especially its reliance on MSIL rewriting. But again, the shortcomings of current implementations
    don&rsquo;t magically eradicate the problem they are trying to address. The gap between human reasoning and
    programming languages remains.</p>
<h2>Designing a language extension mechanism in 2020</h2>
<p>There are two dogma I would like to avoid.</p>
<p>The first is that we should necessarily follow all principles of aspect-oriented programming as you find them in
    Wikipedia. We should not. As much as we cannot dismiss the problem addressed by aspect-oriented programming, we have
    no obligation to follow their canonical implementation strategy. Actually, PostSharp has never been an orthodox
    aspect-oriented framework. It has never relied on the concepts of join points and pointcuts. Unlike AspectJ aspects,
    PostSharp aspects have always had access to the code model. This approach was also the one of Code Analysis (FxCop)
    and now of Roslyn analyzers and source generators. Therefore, we don&rsquo;t have to accept or reject AOP as a
    whole. We can pick what we like and ditch what we don&rsquo;t.</p>
<p>The second and opposite dogma we should avoid is that any mechanism that allows &ldquo;aspects&rdquo; to modify code
    is intrinsically bad. This dogma seems firmly rooted in some Microsoft teams. I&rsquo;m quoting the&nbsp;<a
        href="https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/" target="_blank">introduction blog
        post of Roslyn source generators</a>:</p>
<p>&nbsp;&ldquo;Source Generators are a form of metaprogramming, so it&rsquo;s natural to compare them to similar
    features in other languages like macros [or compiler plug-ins]. The key difference is that Source Generators
    don&rsquo;t allow you _rewrite_ user code. We view this limitation as a significant benefit, since it keeps user
    code predictable with respect to what it actually does at runtime. &ldquo;</p>
<p>This misconception against code rewriting is not productive. It is not possible to bridge the abstraction gap without
    giving aspects (or the compiler plug-in) some way to rewrite code. I agree that it is too dangerous to give the
    aspects an unlimited and unpoliced ability to rewrite the code. But, as my experience with PostSharp proves, it is
    possible to give the aspect the ability to apply some well-defined transformations to some well-defined possible
    target elements of code. These well-defined transformations are:</p>
<ul>
    <li>intercepting any member (method, property, event, field) &ndash; the most important of all;</li>
    <li>wrapping a method in a try/catch/finally;</li>
    <li>adding a new member to a class;</li>
    <li>manipulating custom attributes;</li>
    <li>manipulating managed resources;</li>
    <li>adding new interface implementations.</li>
</ul>
<p>The key point is to make these transformations&nbsp;<em>safely composable</em>. If several interceptors are added to
    the same method, the resulting behavior should be predictable, deterministic and correct.</p>
<p>Ironically, even the add-only nature of Roslyn source generators doesn&rsquo;t make them safe. Two unrelated
    generators can generate two partial classes of the same name that would conflict with each other &ndash; for
    instance both would try to implement IDisposable and would necessarily conflict to introduce the Dispose method.
    This scenario is supported in PostSharp since version 2.0 in 2009, when it was fully redesigned to support strong
    composability. Source generators are a quick and dirty compiler feature. They are useful, but they don&rsquo;t
    sustain a long-term engineering vision.</p>
<p>If you want to implement something like AOP (or any mutable metaprogramming) and really solve the abstraction gap,
    you need two things: read-only access to the source model, and a mechanism to add predefined and safely composable
    (and therefore sortable) transformations to an existing project. These features can be implemented by the compiler
    without affecting the C# language. PostSharp is implemented as a post-compiler MSIL rewriter because it was designed
    at the time where the C# compiler was closed source, but there would be no unsurmountable technical obstacle to
    implement the same approach straight in the compiler.</p>
<p>Making the source code more predictable in presence of interceptors is a solved problem. Hiding implementation
    details from the business source code is intentional so displaying the compiler-generated instructions in the source
    code is actually not desirable. Developers definitively need to know&nbsp;<em>that</em>&nbsp;a method is cached, but
    they don&rsquo;t need to know&nbsp;<em>how</em>&nbsp;it is being cached &ndash; until they really want to focus on
    this aspect. You can visualize the presence of an aspect on a method using code editor enhancements like CodeLens,
    icons, underlining, and tooltips.</p>
<p>Adapting the debugging experience to the presence of aspects is also essential but, again, that problem is already
    partially solved. In PostSharp, you have two modes when you step into a method call: step over aspects and go
    directly to the business code, or step into the aspect. This experience could be further improved by visualizing, at
    debugging time, the list of interceptors present on the method, and offering to step into one of those. There is no
    real conceptual problem there, yet the implementation of our debugger add-in was tremendously difficult.</p>
<p>There is no unsurmountable obstacle to bridging the abstraction gap in C# while maintaining a strongly predictable
    execution model.</p>
<h2>Summary</h2>
<p>The preview release of Roslyn source generators reopened the debate around meta-programming and aspect-oriented
    programming. As often in our industry, we tended to be very technical at the risk of mentally confining ourselves to
    implementation details. By contrast, this essay was an attempt to contemplate the cognitive aspects of programming.
</p>
<p>Hacking is fun and can get things done quickly, but hacking does not scale. Reducing the number of characters needed
    to implement a feature is a noble goal, but not if it makes it more cognitively demanding to maintain the source
    code.</p>
<p>What really limits the software development industry is the scarcity of cognitive resources &ndash; either the
    limited supply of intelligent people, or the finiteness of human intelligence itself. Since we cannot increase our
    intelligence, we must reduce complexity.</p>
<p>In this struggle for simplicity, programming languages have a crucial role to play. As&nbsp;<em>languages</em>, they
    define but also limit what we can express with them and even how we reason. There are unfortunately still things
    that we are able to&nbsp;<em>think</em>&nbsp;but unable to&nbsp;<em>express</em>&nbsp;in C#: for instance the
    implementation of patterns or decomposition of orthogonal concerns. I call that the language abstraction gap.</p>
<p>Aspect-oriented programming was an attempt, 20 years ago, to address that abstraction gap. You may not like its
    canonic implementation in AspectJ or the MSIL rewriting that PostSharp is doing, but just dismissing solutions
    doesn&rsquo;t magically address the need.</p>
<p>The fears of Microsoft&rsquo;s language teams regarding code rewriting are largely unfounded. Yes, designing a
    well-engineered framework for code rewriting is a complex and expensive project, but a feasible one.</p>
<p>A lot of experience has been accumulated in the last decade around the design of aspect-oriented frameworks, and
    about the visualization and debugging of code enhanced with aspects. It has become a mature art. I&rsquo;m proud
    that PostSharp contributed to it.</p>

---

## Site navigation

- Index of the whole site: [llms.txt](../llms.txt)

