将代码添加到使用属性装饰的参数的方法

发布于 2025-02-11 10:58:50 字数 4954 浏览 0 评论 0 原文

因此,我只是为了进行一些个人实验。我已经开发了一堆遵循此接口的验证器:

public interface IParameterValidator<in T>
{
    public void Validate(T param);
}

例如,电子邮件验证器:

public class EmailValidator: IParameterValidator<Email>
{
    private static readonly Regex EmailRegex = new(@"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$");


    public void Validate(Email email)
    {
        if (!EmailRegex.IsMatch(email.ToString())) throw new InvalidParameterException(nameof(email));
    }
}

我想做的是在我可能找到的地方装饰参数,然后通过适当的验证器自动验证它们。因此,这是我认为我需要的属性:

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class ValidateParameterAttribute: Attribute
{
    public Type ValidatorType { get; }

    public ValidateParameterAttribute(Type validatorType)
    {
        if (!validatorType.IsAssignableTo(typeof(IParameterValidator<>))) throw new("Must provide a validator!");
    }
}

据我所知,没有简单的方法可以简单地说“无论我们在方法/属性上看​​到此属性的任何地方,都可以运行此方法”。我也知道我可以通过某种额外的课程来调用我的方法,以寻找这些属性,但我真的不想这样做。沿 MethodInfo.validatedInvoke(object [] params parms)的行。但是,我不想这样做,我希望它“只是工作”。

本质上,我想将其变成

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

这样的东西:将验证器存储在某些寄存器或某些内容中,但这并不重要):

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    Activator.CreateInstance<NameValidator>().Validate(name);
    Activator.CreateInstance<EmailValidator>().Validate(email);
    Activator.CreateInstance<AdultValidator>().Validate(dateOfBirth);
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

因此,我已经有了Google,并且遇到了源生成器。我很确定我可以与其中之一实现这一目标,但是,这不是我以前曾经触过的事情。我认为我是否可以使用源生成器在正确的位置调用此方法,这应该有效:

private string GetValidationString(MethodInfo method)
{
    StringBuilder builder = new();
    ParameterInfo[] parameters = method.GetParameters();
    if (parameters.Length == 0) return string.Empty;
    foreach (ParameterInfo info in parameters)
    {
        ValidateParameterAttribute? validationAttribute = info.GetCustomAttribute<ValidateParameterAttribute>();
        if (validationAttribute is null) continue;
        builder.Append($"Activator.CreateInstance<{validationAttribute.ValidatorType.Name}>().Validate({info.Name});\n");
    }
    return builder.ToString();
}

因此,我的问题确实归结为,我如何让我的源生成器在正确的位置添加该代码?


编辑:

因此,按照评论中的一些建议,我现在试图与 wyere.fody.fody 及其方法拦截器一起使用。

到目前为止,我已经有了这个属性:

[AttributeUsage(AttributeTargets.Method)]
public class ValidatedMethodAttribute: Attribute, IMethodInterceptor
{
    public object Invoke(MethodInfo methodInfo, object instance, Type[] typeArguments, object[] arguments, Func<object[], object> invoker)
    {
        Attribute[]? attributes = methodInfo.GetCustomAttributes().Where(att => att.GetType().IsAssignableTo(typeof(IParameterValidator<>))).ToArray();
        // Do my thing
        return invoker.Invoke(arguments);
    }
}

但是,当我执行构建时,Fody遇到了奇怪的事情,但是我无法弄清楚这个问题是什么..?

  Fody: An unhandled exception occurred:
Exception:
Failed to execute weaver /home/james/.nuget/packages/someta.fody/1.2.1/build/../weaver/Someta.Fody.dll
Type:
System.Exception
StackTrace:
   at InnerWeaver.ExecuteWeavers() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 222
   at InnerWeaver.Execute() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 112
Source:
FodyIsolated
TargetSite:
Void ExecuteWeavers()
System.Type was somehow not found.  Aborting.
Type:
System.InvalidOperationException
StackTrace:
   at Someta.Fody.CecilExtensions.Initialize(ModuleDefinition moduleDefinition, TypeSystem typeSystem, AssemblyNameReference soMeta)
   at Someta.Fody.ModuleWeaver.Execute()
   at InnerWeaver.ExecuteWeavers() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 186
Source:
Someta.Fody
TargetSite:
Boolean Initialize(Mono.Cecil.ModuleDefinition, Fody.TypeSystem, Mono.Cecil.AssemblyNameReference)

我想知道我是否在Linux上构建此问题的事实可能是该错误消息中不存在C驱动器的引用的问题?

So I'm just doing this for a bit of personal experimentation. I've developed a bunch of validators that follow this interface:

public interface IParameterValidator<in T>
{
    public void Validate(T param);
}

For instance, an email validator:

public class EmailValidator: IParameterValidator<Email>
{
    private static readonly Regex EmailRegex = new(@"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))
quot;);


    public void Validate(Email email)
    {
        if (!EmailRegex.IsMatch(email.ToString())) throw new InvalidParameterException(nameof(email));
    }
}

What I'd like to be able to do, is decorate parameters wherever I may find them and then have them automagically validated with the appropriate validator. So this is the attribute I think I need:

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class ValidateParameterAttribute: Attribute
{
    public Type ValidatorType { get; }

    public ValidateParameterAttribute(Type validatorType)
    {
        if (!validatorType.IsAssignableTo(typeof(IParameterValidator<>))) throw new("Must provide a validator!");
    }
}

Now, as far as I'm aware, there's no simple way to simple say "wherever we see this attribute on a method/property, run this method". I also know I could call my method through some sort of extra class that looks for these properties, but I don't really want to do that. Along the lines of MethodInfo.ValidatedInvoke(object[] params parms). However, I don't want to do that, I want it to "just work".

Essentially I'd want to turn this:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

Into something like this (probably store the validators in some register or something but that's not important):

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    Activator.CreateInstance<NameValidator>().Validate(name);
    Activator.CreateInstance<EmailValidator>().Validate(email);
    Activator.CreateInstance<AdultValidator>().Validate(dateOfBirth);
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

So I've had a google around and I've come across Source Generators. I'm fairly sure that I can achieve this with one of those, however, it's not something I've ever touched before. I reckon if I can work out how to call this method in the right places using the source generator, this should work:

private string GetValidationString(MethodInfo method)
{
    StringBuilder builder = new();
    ParameterInfo[] parameters = method.GetParameters();
    if (parameters.Length == 0) return string.Empty;
    foreach (ParameterInfo info in parameters)
    {
        ValidateParameterAttribute? validationAttribute = info.GetCustomAttribute<ValidateParameterAttribute>();
        if (validationAttribute is null) continue;
        builder.Append(
quot;Activator.CreateInstance<{validationAttribute.ValidatorType.Name}>().Validate({info.Name});\n");
    }
    return builder.ToString();
}

So my question really boils down to, how do I get my source generator to add that code in the right place?


EDIT:

So following some advice in the comments, I'm now trying to get this working with Someta.Fody and its method interceptors.

So far, I've got this attribute:

[AttributeUsage(AttributeTargets.Method)]
public class ValidatedMethodAttribute: Attribute, IMethodInterceptor
{
    public object Invoke(MethodInfo methodInfo, object instance, Type[] typeArguments, object[] arguments, Func<object[], object> invoker)
    {
        Attribute[]? attributes = methodInfo.GetCustomAttributes().Where(att => att.GetType().IsAssignableTo(typeof(IParameterValidator<>))).ToArray();
        // Do my thing
        return invoker.Invoke(arguments);
    }
}

However, when I execute the build, something weird is going on with Fody but I can't quite work out what this issue is..?

  Fody: An unhandled exception occurred:
Exception:
Failed to execute weaver /home/james/.nuget/packages/someta.fody/1.2.1/build/../weaver/Someta.Fody.dll
Type:
System.Exception
StackTrace:
   at InnerWeaver.ExecuteWeavers() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 222
   at InnerWeaver.Execute() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 112
Source:
FodyIsolated
TargetSite:
Void ExecuteWeavers()
System.Type was somehow not found.  Aborting.
Type:
System.InvalidOperationException
StackTrace:
   at Someta.Fody.CecilExtensions.Initialize(ModuleDefinition moduleDefinition, TypeSystem typeSystem, AssemblyNameReference soMeta)
   at Someta.Fody.ModuleWeaver.Execute()
   at InnerWeaver.ExecuteWeavers() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 186
Source:
Someta.Fody
TargetSite:
Boolean Initialize(Mono.Cecil.ModuleDefinition, Fody.TypeSystem, Mono.Cecil.AssemblyNameReference)

I'm wondering if the fact that I'm building this on Linux might be an issue given the references to a non-existent C drive in that error message?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

忆依然 2025-02-18 10:58:50

不幸的是,简短的答案是,不可能通过来源生成器来达到您希望的精确解决方案。原因是源发电机的绝对设计理念之一(我感到沮丧,但可以理解理由)是通过使用源发电机来积极地禁止现有源文件突变。换句话说,您实际上无法修改任何手写类的代码。源生成器为。它仍然可以非常强大的原因是通过使用部分类(其定义在多个文件中声明的类型)。

有了这个序言,让我们更仔细地看一下您的确切用例。您有此方法:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

您想将其转换为此方法:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    Activator.CreateInstance<NameValidator>().Validate(name);
    Activator.CreateInstance<EmailValidator>().Validate(email);
    Activator.CreateInstance<AdultValidator>().Validate(dateOfBirth);
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

如您所见,您想要的结果需要修改您的 createuser 方法,这是不可能的。

一个可以使您一半的解决方案是为任何方法生成一种用 validateParameter 属性的参数生成自定义验证方法。例如,您可以生成此方法:

// Generated
private void ValidateCreateUser(Name name, Email email, DateOfBirth dateOfBirth)
{
    Activator.CreateInstance<NameValidator>().Validate(name);
    Activator.CreateInstance<EmailValidator>().Validate(email);
    Activator.CreateInstance<AdultValidator>().Validate(dateOfBirth);
}

然后在您的 createuser 方法中,您可以直接自己调用此方法:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    ValidateCreateUser(name, email, dateOfBirth);  // You'd add this line yourself
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

这是否是一个足够成功的结果,只有您可以说。

如果您想准确地找到所希望的解决方案,恐怕您在评论中的怀疑是您必须求助于Fody是准确的。创建自定义FODY插件需要对Fody和C#IL有相当深的了解。

无耻的插头:我维护了一个名为 lyea 的fody库这将使您很容易通过 method interceptors> method Interpectors

Unfortunately, the short answer is that it will not be possible to arrive at the exact solution you are hoping for with source generators. The reason is that one of the absolute design philosophies of source generators (which I find dismaying, but can understand the rationale) was to actively disallow mutation of existing source files through the use of source generators. To put it another way, you can't actually modify the code of any of your hand-written classes. Source generators are strictly additive. The reason it can still be very powerful is through the use of partial classes (a type whose definition is declared in multiple files).

With that preamble out of the way, let's look a bit more closely at your exact use-case. You have this method:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

And you want to turn it into this method:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    Activator.CreateInstance<NameValidator>().Validate(name);
    Activator.CreateInstance<EmailValidator>().Validate(email);
    Activator.CreateInstance<AdultValidator>().Validate(dateOfBirth);
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

As you can see, the outcome you desire requires modifying your CreateUser method, which isn't possible.

One solution that will get you half-way there would be to generate a custom validate method for any method with a parameter decorated with the ValidateParameter attribute. For example, you could generate this method:

// Generated
private void ValidateCreateUser(Name name, Email email, DateOfBirth dateOfBirth)
{
    Activator.CreateInstance<NameValidator>().Validate(name);
    Activator.CreateInstance<EmailValidator>().Validate(email);
    Activator.CreateInstance<AdultValidator>().Validate(dateOfBirth);
}

Then in your CreateUser method you would be able to call this method directly yourself:

public User CreateUser( [ValidateParameter(typeof(NameValidator))] Name name,
                        [ValidateParameter(typeof(EmailValidator))] Email email,
                        [ValidateParameter(typeof(AdultValidator))] DateOfBirth dateOfBirth)
{
    ValidateCreateUser(name, email, dateOfBirth);  // You'd add this line yourself
    User user = new(name, email, dateOfBirth);
    _userRepository.Add(user);
    return user;
}

Whether this is a sufficiently successful outcome only you can say.

If you want to get to exactly the solution you were hoping for, I'm afraid your suspicion in the comments that you'd have to resort to Fody is accurate. Creating a custom Fody plugin requires a pretty deep understanding of both Fody and the C# IL.

Shameless plug: I maintain a Fody library called Someta that would make it pretty easy for you to accomplish this via method interceptors.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文