从 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();
}
}
这是我的方法
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();
}
}