Search code examples
c#validationpostsharpsystem.reflection

How to get the top most type of an instance variable?


Say I have an instance variable that is declared to be a given base class. I want to find what the original, upper-most type that object actually is instead of the base class. How do I do this?

I have a validation attribute a la PostSharp so the reflection hit is at compile-time and is therefore moot thanks to the CompileTimeValidation method. I'm simply at a loss of how I'd do this. Doing an IsSubclassOf scan isn't helpful as I'd get multiple false positives.

To give context, I have a base Entity type. I derive from this type all over defining all kinds of Entity types. I decorate these types, including the base Entity, with a policy attribute. The PostSharp aspect verifies at compile-time certain policy constraints. I would like to be able to only decorate the base Entity type with the aspect validation so that Entity and all its derived types will be validated. I can see the validation already happen. However, it's handled as an Entity and not DerivedEntity. In order to evaluate the policy on DerviedEntity, I need to decorate that class specifically. I would like to not do this if possible and only decorate Entity.

Essentially, I want to centralize my validation on the base class as the validation schema is the same for all derived classes. However, the values in the derived classes can change and I need to do some bounds checking.

Edit: Let's add some code.

[EnforceMaxLifetimePolicy]
[LifetimePolicy]
public class Entity<T>
{
    public string Key { get; set; }
    public T Object { get; set; }

    public TimeSpan EntityLifetime
    {
        get
        {
            var lifetimePolicy =
                Attribute.GetCustomAttribute(GetType(), typeof(LifetimePolicyAttribute)) as LifetimePolicyAttribute;

            return new TimeSpan(lifetimePolicy.Hours, lifetimePolicy.Minutes, 0);
        }
    }
}

[Serializable]
[AttributeUsage(AttributeTargets.Class)]
internal class LifetimePolicyAttribute : Attribute
{
    public readonly short Hours;
    public readonly short Minutes;

    public LifetimePolicyAttribute(short hours, short minutes)
    {
        Hours = hours;
        Minutes = minutes;
    }

    public LifetimePolicyAttribute()
    {
        Minutes = 1;
    }
}

[Serializable]
[AttributeUsage(AttributeTargets.Class)]
internal class EnforceMaxLifetimePolicyAttribute : OnMethodBoundaryAspect
{
    public override bool CompileTimeValidate(MethodBase method)
    {
        var type = method.GetType();

        var lifetimePolicy = GetCustomAttribute(type, typeof(LifetimePolicyAttribute)) as LifetimePolicyAttribute;

        if (lifetimePolicy != null && lifetimePolicy.Hours + lifetimePolicy.Minutes / 60 > 24)
        {
            throw new InvalidAnnotationException($"Lifetimes can not exceed 24 hours. The lifetime on {type.FullName} is invalid.");
        }

        return true;
    }
}

[LifetimePolicy(hours: 24, minutes: 0)]
internal class ShoppingCartEntity : Entity<ShoppingCart>
{
}

As you can see, ShoppingCartEntity does not have the [EnforceMaxLifetimePolicy] on it. It's on the base class Entity. However, I still want the Enforce attribute entity to also apply to derived types which is why I leave the Inherited flag to its default (true).


Solution

  • Apparently, PostSharp obviates this need. It supports automatic application to derived types via Multicasting. So, the lifetime attribute looks like so:

    [Serializable]
    [AttributeUsage(AttributeTargets.Class)]
    [MulticastAttributeUsage(Inheritance = MulticastInheritance.Multicast)]
    internal class EnforceMaxLifetimePolicyAttribute : OnMethodBoundaryAspect
    {
        public override bool CompileTimeValidate(MethodBase method)
        {
            var type = method.DeclaringType;
    
            var lifetimePolicy = GetCustomAttribute(type, typeof(LifetimePolicyAttribute)) as LifetimePolicyAttribute;
    
            if (lifetimePolicy.Hours + lifetimePolicy.Minutes / 60 > 24)
            {
                throw new InvalidAnnotationException($"Lifetimes can not exceed 24 hours. The lifetime on {type.FullName} is invalid.");
            }
    
            return true;
        }
    }
    

    The [MulticastAttributeUsage(Inheritance = MulticastInheritance.Multicast)] line will have PostSharp automatically apply it to the ShoppingCartEntity and others.