Implement interfaces based on class properties without reflection

别来无恙 提交于 2019-12-21 18:47:57

问题


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 on a large number of classes. This may be INotifyPropertyChanged, IDispose, IEquatable or some custom interface that you have created.

I'd like to write a custom aspect that implements a general version of IEquatable based on the properties of the class it's applied to (preferably at compile-time instead of by using reflection at runtime). It would be good to just be able to add an attribute to a simple class rather than having to implement a custom method each time. Is that possible? I'd hope so, since it's specifically called out in this introduction, but I haven't been able to track down any example code.

I've seen this example from the PostSharp website that includes an example of introducing the IIdentifiable interface. But it just returns a GUID that's independent of the class that the new interface is added to.

Is there a way to construct a custom attribute that implements IEquatable based on the properties of the type that it's applied to (i.e. making two instances equal if all of their properties are equal)?

I've found a solution using T4 templates but would like to know if the same can be achieved using PostSharp.

Edit:

To be clear, I'd like to be able to write something like this:

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

and have it 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;
    }
}

回答1:


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));
        }
    }

}



回答2:


I'm afraid this can not be done with PostSharp. PostSharp "injects" aspects code in your clases but you have to code the aspects. The key here is indetify common behavior and cross cutting concern in your system and model it as Aspects.

In the example of IIdentifiable you can see how GUID is a unique identifier that can be use by a lot of different classes in your system. It is common code, it is cross cutting concern and you find yourself REPEATING code in all your class entities so Identificable can be modeled as Aspect and get rid of repeating code.

As diferent classes has diferent Equals implementation you can not "deatach" (convert to aspect) the implementation of Equals. Equals is not a common behavior. Equals is not cross cutting concern. Equals can not be an Aspect (without reflection).



来源:https://stackoverflow.com/questions/26005468/implement-interfaces-based-on-class-properties-without-reflection

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!