// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; namespace dnlib.DotNet { /// /// A custom attribute /// public sealed class CustomAttribute : ICustomAttribute { ICustomAttributeType ctor; byte[] rawData; readonly IList arguments; readonly IList namedArguments; uint caBlobOffset; /// /// Gets/sets the custom attribute constructor /// public ICustomAttributeType Constructor { get => ctor; set => ctor = value; } /// /// Gets the attribute type /// public ITypeDefOrRef AttributeType => ctor?.DeclaringType; /// /// Gets the full name of the attribute type /// public string TypeFullName { get { if (ctor is MemberRef mrCtor) return mrCtor.GetDeclaringTypeFullName() ?? string.Empty; if (ctor is MethodDef mdCtor) { var declType = mdCtor.DeclaringType; if (declType is not null) return declType.FullName; } return string.Empty; } } /// /// Gets the name of the attribute type /// internal string TypeName { get { if (ctor is MemberRef mrCtor) return mrCtor.GetDeclaringTypeName() ?? string.Empty; if (ctor is MethodDef mdCtor) { var declType = mdCtor.DeclaringType; if (declType is not null) return declType.Name; } return string.Empty; } } /// /// true if the raw custom attribute blob hasn't been parsed /// public bool IsRawBlob => rawData is not null; /// /// Gets the raw custom attribute blob or null if the CA was successfully parsed. /// public byte[] RawData => rawData; /// /// Gets all constructor arguments /// public IList ConstructorArguments => arguments; /// /// true if is not empty /// public bool HasConstructorArguments => arguments.Count > 0; /// /// Gets all named arguments (field and property values) /// public IList NamedArguments => namedArguments; /// /// true if is not empty /// public bool HasNamedArguments => namedArguments.Count > 0; /// /// Gets all s that are field arguments /// public IEnumerable Fields { get { var namedArguments = this.namedArguments; int count = namedArguments.Count; for (int i = 0; i < count; i++) { var namedArg = namedArguments[i]; if (namedArg.IsField) yield return namedArg; } } } /// /// Gets all s that are property arguments /// public IEnumerable Properties { get { var namedArguments = this.namedArguments; int count = namedArguments.Count; for (int i = 0; i < count; i++) { var namedArg = namedArguments[i]; if (namedArg.IsProperty) yield return namedArg; } } } /// /// Gets the #Blob offset or 0 if unknown /// public uint BlobOffset => caBlobOffset; /// /// Constructor /// /// Custom attribute constructor /// Raw custom attribute blob public CustomAttribute(ICustomAttributeType ctor, byte[] rawData) : this(ctor, null, null, 0) => this.rawData = rawData; /// /// Constructor /// /// Custom attribute constructor public CustomAttribute(ICustomAttributeType ctor) : this(ctor, null, null, 0) { } /// /// Constructor /// /// Custom attribute constructor /// Constructor arguments or null if none public CustomAttribute(ICustomAttributeType ctor, IEnumerable arguments) : this(ctor, arguments, null) { } /// /// Constructor /// /// Custom attribute constructor /// Named arguments or null if none public CustomAttribute(ICustomAttributeType ctor, IEnumerable namedArguments) : this(ctor, null, namedArguments) { } /// /// Constructor /// /// Custom attribute constructor /// Constructor arguments or null if none /// Named arguments or null if none public CustomAttribute(ICustomAttributeType ctor, IEnumerable arguments, IEnumerable namedArguments) : this(ctor, arguments, namedArguments, 0) { } /// /// Constructor /// /// Custom attribute constructor /// Constructor arguments or null if none /// Named arguments or null if none /// Original custom attribute #Blob offset or 0 public CustomAttribute(ICustomAttributeType ctor, IEnumerable arguments, IEnumerable namedArguments, uint caBlobOffset) { this.ctor = ctor; this.arguments = arguments is null ? new List() : new List(arguments); this.namedArguments = namedArguments is null ? new List() : new List(namedArguments); this.caBlobOffset = caBlobOffset; } /// /// Constructor /// /// Custom attribute constructor /// Constructor arguments. The list is now owned by this instance. /// Named arguments. The list is now owned by this instance. /// Original custom attribute #Blob offset or 0 internal CustomAttribute(ICustomAttributeType ctor, List arguments, List namedArguments, uint caBlobOffset) { this.ctor = ctor; this.arguments = arguments ?? new List(); this.namedArguments = namedArguments ?? new List(); this.caBlobOffset = caBlobOffset; } /// /// Gets the field named /// /// Name of field /// A instance or null if not found public CANamedArgument GetField(string name) => GetNamedArgument(name, true); /// /// Gets the field named /// /// Name of field /// A instance or null if not found public CANamedArgument GetField(UTF8String name) => GetNamedArgument(name, true); /// /// Gets the property named /// /// Name of property /// A instance or null if not found public CANamedArgument GetProperty(string name) => GetNamedArgument(name, false); /// /// Gets the property named /// /// Name of property /// A instance or null if not found public CANamedArgument GetProperty(UTF8String name) => GetNamedArgument(name, false); /// /// Gets the property/field named /// /// Name of property/field /// true if it's a field, false if it's a property /// A instance or null if not found public CANamedArgument GetNamedArgument(string name, bool isField) { var namedArguments = this.namedArguments; int count = namedArguments.Count; for (int i = 0; i < count; i++) { var namedArg = namedArguments[i]; if (namedArg.IsField == isField && UTF8String.ToSystemStringOrEmpty(namedArg.Name) == name) return namedArg; } return null; } /// /// Gets the property/field named /// /// Name of property/field /// true if it's a field, false if it's a property /// A instance or null if not found public CANamedArgument GetNamedArgument(UTF8String name, bool isField) { var namedArguments = this.namedArguments; int count = namedArguments.Count; for (int i = 0; i < count; i++) { var namedArg = namedArguments[i]; if (namedArg.IsField == isField && UTF8String.Equals(namedArg.Name, name)) return namedArg; } return null; } /// public override string ToString() => TypeFullName; } /// /// A custom attribute constructor argument /// public struct CAArgument : ICloneable { TypeSig type; object value; /// /// Gets/sets the argument type /// public TypeSig Type { readonly get => type; set => type = value; } /// /// Gets/sets the argument value /// public object Value { readonly get => value; set => this.value = value; } /// /// Constructor /// /// Argument type public CAArgument(TypeSig type) { this.type = type; value = null; } /// /// Constructor /// /// Argument type /// Argument value public CAArgument(TypeSig type, object value) { this.type = type; this.value = value; } readonly object ICloneable.Clone() => Clone(); /// /// Clones this instance and any s and s /// referenced from this instance. /// /// public readonly CAArgument Clone() { var value = this.value; if (value is CAArgument) value = ((CAArgument)value).Clone(); else if (value is IList args) { var newArgs = new List(args.Count); int count = args.Count; for (int i = 0; i < count; i++) { var arg = args[i]; newArgs.Add(arg.Clone()); } value = newArgs; } return new CAArgument(type, value); } /// public override readonly string ToString() => $"{value ?? "null"} ({type})"; } /// /// A custom attribute field/property argument /// public sealed class CANamedArgument : ICloneable { bool isField; TypeSig type; UTF8String name; CAArgument argument; /// /// true if it's a field /// public bool IsField { get => isField; set => isField = value; } /// /// true if it's a property /// public bool IsProperty { get => !isField; set => isField = !value; } /// /// Gets/sets the field/property type /// public TypeSig Type { get => type; set => type = value; } /// /// Gets/sets the property/field name /// public UTF8String Name { get => name; set => name = value; } /// /// Gets/sets the argument /// public CAArgument Argument { get => argument; set => argument = value; } /// /// Gets/sets the argument type /// public TypeSig ArgumentType { get => argument.Type; set => argument.Type = value; } /// /// Gets/sets the argument value /// public object Value { get => argument.Value; set => argument.Value = value; } /// /// Default constructor /// public CANamedArgument() { } /// /// Constructor /// /// true if field, false if property public CANamedArgument(bool isField) => this.isField = isField; /// /// Constructor /// /// true if field, false if property /// Field/property type public CANamedArgument(bool isField, TypeSig type) { this.isField = isField; this.type = type; } /// /// Constructor /// /// true if field, false if property /// Field/property type /// Name of field/property public CANamedArgument(bool isField, TypeSig type, UTF8String name) { this.isField = isField; this.type = type; this.name = name; } /// /// Constructor /// /// true if field, false if property /// Field/property type /// Name of field/property /// Field/property argument public CANamedArgument(bool isField, TypeSig type, UTF8String name, CAArgument argument) { this.isField = isField; this.type = type; this.name = name; this.argument = argument; } object ICloneable.Clone() => Clone(); /// /// Clones this instance and any s referenced from this instance. /// /// public CANamedArgument Clone() => new CANamedArgument(isField, type, name, argument.Clone()); /// public override string ToString() => $"({(isField ? "field" : "property")}) {type} {name} = {Value ?? "null"} ({ArgumentType})"; } }