从 C# 中的参数属性访问对象值

Access object value from parameter attribute in C#

这是我的方法

public Component SaveComponent([ValidateMetaFields] Component componentToSave) {
    ...
}

这是我的自定义属性:

[AttributeUsage(AttributeTargets.Parameter)]
public class ValidateMetaFieldsAttribute : Attribute
{
    public ValidateMetaFieldsAttribute()
    {
        // how to access `componentToSave` here?
    }
}

我想知道有没有办法从 ValidateMetaFieldsAttribute 访问 componentToSave 对象?我找不到任何示例代码或示例。

不,属性实例对其应用的目标没有任何概念。

请注意,通常您从 目标 获取属性,因此无论做什么,获取都可能为接下来发生的任何事情提供信息。可能有点烦人,但希望不是不可行的。

所有这一切的一个小例外是呼叫者信息属性 - 如果您使用类似

[AttributeUsage(AttributeTargets.Parameter)]
public class ValidateMetaFieldsAttribute : Attribute
{
    public ValidateMetaFieldsAttribute([CallerMemberName] string member = null)
    {
        ...
    }
}

... 那么在这种情况下,编译器将填充 方法 名称 (SaveComponent),即使该属性已应用于参数。同样,您可以获取文件路径和行号。

然而,鉴于此关于目的的评论,我认为您遇到了更大的问题:

To validate componentToSave and throw an exception before method body even runs.

属性构造函数中的代码只有在获取属性时才会执行。例如,它 not 在每次方法调用时执行。这很可能使您期望的一切变得不可行。

您可能想要研究 AOP,例如PostSharp.

您可以使用 Mono.Cecil

实现此目的

在你的主项目中

为所有验证器定义一个通用接口:

public interface IArgumentValidator
{
    void Validate(object argument);
}

现在,像这样重写你的 ValidateMetaFieldsAttribute

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class ValidateMetaFieldsAttribute : Attribute, IArgumentValidator
{
    public void Validate(object argument)
    {
        //todo: put your validation logic here
    }
}

创建您自己的 IL-Rewriter

创建另一个控制台应用程序,将 Mono.Cecil 添加为 nuget
打开你的主项目:

var assembly = AssemblyDefinition.ReadAssembly(@"ClassLibrary1.dll"); // your project assembly
var module = assembly.MainModule;

找到IArgumentValidator和所有后代验证器:

var validatorInterface = module.Types
  .FirstOrDefault(t => t.IsInterface && t.Name == "IArgumentValidator");
var validators = module.Types
  .Where(t => t.Interfaces.Contains(validatorInterface)).ToArray();

然后你需要找到所有使用验证器的类型:

var typesToPatch = module.Types.Select(t => new
{
    Type = t,
    Validators = 
        t.Methods.SelectMany(
         m => m.Parameters.SelectMany(
          p => p.CustomAttributes.Select(a => a.AttributeType)))
        .Distinct()
        .ToArray()
})
.Where(x => x.Validators.Any(v => validators.Contains(v)))
.ToArray();

现在,在每个找到的类型中,您需要添加该类型中使用的所有验证器(作为字段)

foreach (var typeAndValidators in typesToPatch)
{
    var type = typeAndValidators.Type;
    var newFields = new Dictionary<TypeReference, FieldDefinition>();

    const string namePrefix = "e47bc57b_"; // part of guid
    foreach (var validator in typeAndValidators.Validators)
    {
        var fieldName = $"{namePrefix}{validator.Name}";
        var fieldDefinition = new FieldDefinition(fieldName, FieldAttributes.Private, validator);
        type.Fields.Add(fieldDefinition);
        newFields.Add(validator, fieldDefinition);
    }

目前所有新字段都是空的,因此应该对其进行初始化。我在新方法中进行了初始化:

var initFields = new MethodDefinition($"{namePrefix}InitFields", MethodAttributes.Private, module.TypeSystem.Void);
foreach (var field in newFields)
{
    initFields.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
    initFields.Body.Instructions.Add(Instruction.Create(OpCodes.Newobj, field.Key.Resolve().GetConstructors().First()));
    initFields.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, field.Value));
}
initFields.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
type.Methods.Add(initFields);

但这还不够,因为此方法从未调用过。要解决此问题,您还需要修补当前类型的所有构造函数:

var ctors = type.GetConstructors().ToArray();
var rootCtors = ctors.Where(c =>
    !c.Body.Instructions.Any(i => i.OpCode == OpCodes.Call
    && ctors.Except(new []{c}).Any(c2 => c2.Equals(i.Operand)))).ToArray();
foreach (var ctor in rootCtors)
{
    var retIdx = ctor.Body.Instructions.Count - 1;
    ctor.Body.Instructions.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
    ctor.Body.Instructions.Insert(retIdx + 1, Instruction.Create(OpCodes.Call, initFields));
}

(这里有些棘手的部分是 rootCtors。正如我之前所说,您可以修补所有构造函数,但这不是必需的,因为某些构造函数可能会调用其他构造函数)

我们需要对当前类型做的最后一件事是修补每个具有验证器的方法

foreach (var method in type.Methods)
{
    foreach (var parameter in method.Parameters)
    {
        foreach (var attribute in parameter.CustomAttributes)
        {
            if (!validators.Contains(attribute.AttributeType))
                continue;

            var field = newFields[attribute.AttributeType];
            var validationMethod = field.FieldType.Resolve().Methods.First(m => m.Name == "Validate");
            method.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldarg_0));
            method.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Ldfld, field));
            method.Body.Instructions.Insert(2, Instruction.Create(OpCodes.Ldarg_S, parameter));
            method.Body.Instructions.Insert(3, Instruction.Create(OpCodes.Callvirt, validationMethod));
        }
    }
}

所有类型都打好补丁后你可以保存修改后的程序集

assembly.Write("PatchedAssembly.dll");

您可以在单个文件中找到所有这些代码 here


工作示例(来自 dotPeek)
来源 class:

public class Demo
{
    public Component SaveComponent([ValidateMetaFields] Component componentToSave)
    {
        return componentToSave;
    }
}

已修补 class:

public class Demo
{
  private ValidateMetaFieldsAttribute e47bc57b_ValidateMetaFieldsAttribute;

  public Component SaveComponent([ValidateMetaFields] Component componentToSave)
  {
    this.e47bc57b_ValidateMetaFieldsAttribute.Validate((object) componentToSave);
    return componentToSave;
  }

  public Demo()
  {
    this.e47bc57b_InitFields();
  }

  private void e47bc57b_InitFields()
  {
    this.e47bc57b_ValidateMetaFieldsAttribute = new ValidateMetaFieldsAttribute();
  }
}