简体   繁体   中英

Inject method parameter at compiletime using Roslyn

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.

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

例外

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM