2

I'm trying to find out wether Roslyn does support the following use-case:

I use a Guard class for parameter validation

public void Foo(string bar)
{
    Guard.NotNull(bar, nameof(bar));
    // ....
}

Nothing fancy and the nameof(...) expression even makes it refactoring-friendly. But it's still redundant. And nothing prevents me from doing something like this

public void Foo(string bar, string baz)
{
    Guard.NotNull(bar, nameof(baz));
    // ...
}

So if there were a way to avoid the nameof(...) part completely that would be nice.

So I would like to enable Roslyn to do something similar to the [CallerMemberNameattribute], just for parameters.

public static class Guard
{
    public static void NotNull(object param, [CallerParameterName]string paramName = "")
    {
        if (param == null)
        {
            throw new ArgumentNullException(paramName);
        }
    }
}

public void Foo(string bar)
{
    Guard.NotNull(bar);
    // ...
}

I don't want to change the code before compiling it (like a refactoring or code fix would). I don't want to see values for arguments annotated with the CallerParameterNameAttribute in source code at all (exactly like I don't see them for CallerMemberName or CallerLineNumber etc.).

I want Roslyn to inject the names of the parameters at compiletime for me.

Both Source Generators and Code Generators can only add source code (not change it) and compile it. The regular Roslyn analyzers also change source code afaik.

Any idea if that part of Roslyn is publicly accessible and where I can find information on how to use it?


Update: I don't want to use an IL Weaver like PostSharp. Just teach Roslyn to treat my custom attribute like one of the System.Runtime.CompilerService attributes.

Sebastian Weber
  • 6,766
  • 2
  • 30
  • 49

3 Answers3

1

I'm not using Roslyn for this answer but this maybe help

You can use reflection to get the member expression and throw the exception with the argument name:

public class Guard
{
    public static void NotNull<T1>(Expression<Func<T1>> expression)
    {
        Func<T1> check = expression.Compile();
        if (check() is null)
        {
            var member = expression.Body;
            if (member is MemberExpression)
            {
                string name = ((MemberExpression)member).Member.Name;
                throw new ArgumentNullException(name);
            }
        }
    }
}

public class Program
{
    public static void Foo(string myParameterName)
    {
        Guard.NotNull(() => myParameterName);
    }

    public static void Main(string[] args)
    {
        Foo(null);
        return;
    }
}

So, the expcetion will be throw the original parameter name: myPropertyName

exception

I also tested in Release configuration (-c Release) to check if myParameterName was not changed to another compiled name but not fully investigated this.

The downside of this code is the usage of a lambda expression, reflection (to not mention call of .Compile) to simple check if a variable is null with can be impactful in performance.

Leo
  • 1,990
  • 1
  • 13
  • 20
  • 1
    The downside of your solution is that performance will take a very hard hit. We are talking several orders of magnitude between using a simple constant string and an expression tree. A couple of years ago I did a performance evaluation because we made heavy use of that pattern in a project. While fast enough for implementing `INotifyPropertyChanged` it killed us when we used it for parameter validation across the whole application. – Sebastian Weber Aug 21 '20 at 05:07
  • totally agree @SebastianWeber, to check for null arguments is better to simple check direct with Guard.NotNull(parameter) instead of lamba and a expression analyzer – Leo Aug 21 '20 at 11:58
0

You can do with Fody: https://github.com/Fody/Fody . E.g. https://github.com/Fody/NullGuard .

NN_
  • 1,593
  • 1
  • 10
  • 26
  • From the project description this sounds similar to PostSharp in that it modifies the compiled IL instead of using Roslyn to modify the compilation itself? That's not really what I want to do though. – Sebastian Weber Aug 15 '20 at 22:34
0

This is the [CallerArgumentExpression] attribute in C# 10:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression

Spi
  • 686
  • 3
  • 18