Deploy interfaces based on class properties without reflection

This page on the PostSharp website has the following teaser:

One of the common situations that you will encounter is the need to implement a specific interface for a large number of classes. It may be INotifyPropertyChanged, IDispose, IEquatableor any user interface that you created.

I would like to write a custom aspect that implements a generic version IEquatablebased on the properties of the class to which it is applicable (preferably at compile time, and not by reflection at runtime). It would be nice to just add the attribute to a simple class, and not execute an individual method every time. Is it possible? I hope so, because in this introduction he specifically called, but I could not find any sample code.

I saw this example on the PostSharp website, which includes an example of introducing an interface IIdentifiable. But it just returns GUIDwhich is independent of the class to which the new interface is added.

Is there a way to create a custom attribute that implements IEquatablebased on the properties of the type to which it is applicable (i.e. makes two instances equal if all their properties are equal)?

I found a solution using T4 templates , but would like to know if this can be achieved using PostSharp.

Edit:

To be clear, I would like to write something like this:

[AutoEquatable]
public class Thing
{
    int Id { get; set; }
    string Description { get; get; }
}

and automatically converted to this:

public class Thing
{
    int Id { get; set; }
    string Description { get; get; }

    public override bool Equals(object other)
    {
        Thing o = other as Thing;
        if (o == null) return false;

        // generated in a loop based on the properties
        if (!Id.Equals(o.Id)) return false;
        if (!Description.Equals(o.Description)) return false;

        return true;
    }
}
+4
source share
2 answers

This is possible with PostSharp 4.0 using the following code:

[PSerializable]
class EquatableAttribute : InstanceLevelAspect, IAdviceProvider
{

    public List<ILocationBinding> Fields;

    [ImportMember("Equals", IsRequired = true, Order = ImportMemberOrder.BeforeIntroductions)]
    public Func<object, bool> EqualsBaseMethod;


    [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
    public new bool Equals(object other)
    {
        // TODO: Define a smarter way to determine if base.Equals should be invoked.
        if (this.EqualsBaseMethod.Method.DeclaringType != typeof(object) )
        {
            if (!this.EqualsBaseMethod(other))
                return false;
        }

        object instance = this.Instance;
        foreach (ILocationBinding binding in this.Fields)
        {
            // The following code is inefficient because it boxes all fields. There is currently no workaround.
            object thisFieldValue = binding.GetValue(ref instance, Arguments.Empty);
            object otherFieldValue = binding.GetValue(ref other, Arguments.Empty);

            if (!object.Equals(thisFieldValue, otherFieldValue))
                return false;
        }

        return true;
    }

    // TODO: Implement GetHashCode the same way.

    public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
    {
        Type targetType = (Type) targetElement;
        FieldInfo bindingField = this.GetType().GetField("Fields");

        foreach (
            FieldInfo field in
                targetType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public |
                                     BindingFlags.NonPublic))
        {
            yield return new ImportLocationAdviceInstance(bindingField, new LocationInfo(field));
        }
    }

}
+4
source

, PostSharp. PostSharp "" , . , Aspects.

IIdentifiable , GUID - , . , , , REPEATING , Identificable Aspect .

Equals, "" ( ) Equals. - . . ( ).

+1

All Articles