基于类属性实现接口,无需反射 [英] Implement interfaces based on class properties without reflection

查看:28
本文介绍了基于类属性实现接口,无需反射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此页面 PostSharp 网站上有以下预告:

<块引用>

您会遇到的一种常见情况是需要在大量类上实现特定接口.这可能是 INotifyPropertyChangedIDisposeIEquatable 或您创建的某些自定义界面.

我想编写一个自定义方面,它根据应用到的类的属性来实现 IEquatable 的一般版本(最好在编译时而不是在运行时使用反射).能够向简单的类添加属性而不是每次都实现自定义方法会很好.那可能吗?我希望如此,因为它在本介绍中特别提及,但我一直无法找到任何示例代码.

我见过 这个例子来自 PostSharp 网站,其中包括一个介绍 IIdentifiable 接口的例子.但它只返回一个 GUID,它与添加新接口的类无关.

有没有一种方法可以根据所应用的类型的属性来构造实现 IEquatable 的自定义属性(即,如果两个实例的所有属性都相等,则使它们相等)?

我找到了使用 T4 模板的解决方案,但想知道是否可以使用PostSharp.

明确地说,我希望能够写出这样的东西:

[AutoEquatable]公共课的东西{int Id { 获取;放;}字符串 描述 { 获取;得到;}}

并自动转换为:

公共类事物{int Id { 获取;放;}字符串 描述 { 获取;得到;}公共覆盖布尔等于(对象其他){事物 o = 其他事物;如果(o == null)返回false;//根据属性在循环中生成如果 (!Id.Equals(o.Id)) 返回 false;if (!Description.Equals(o.Description)) 返回假;返回真;}}

解决方案

使用以下代码在 PostSharp 4.0 中可以做到这一点;

[PSerializable]类 EquatableAttribute : InstanceLevelAspect, IAdviceProvider{公共列表<ILocationBinding>领域;[ImportMember("Equals", IsRequired = true, Order = ImportMemberOrder.BeforeIntroductions)]public FuncEqualsBaseMethod;[IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]公共新布尔等于(对象其他){//TODO:定义一种更智能的方法来确定是否应该调用 base.Equals.if (this.EqualsBaseMethod.Method.DeclaringType != typeof(object) ){if (!this.EqualsBaseMethod(other))返回假;}对象实例 = this.Instance;foreach(this.Fields 中的 ILocationBinding 绑定){//以下代码效率低下,因为它将所有字段都装箱了.目前没有解决方法.object thisFieldValue = binding.GetValue(ref instance, Arguments.Empty);object otherFieldValue = binding.GetValue(ref other, Arguments.Empty);if (!object.Equals(thisFieldValue, otherFieldValue))返回假;}返回真;}//TODO: 以同样的方式实现 GetHashCode.公共 IEnumerable提供建议(对象目标元素){类型 targetType = (Type) targetElement;FieldInfo bindingField = this.GetType().GetField("Fields");foreach (FieldInfo 字段在targetType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public |BindingFlags.NonPublic)){yield return new ImportLocationAdviceInstance(bindingField, new LocationInfo(field));}}}

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

解决方案

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

}

这篇关于基于类属性实现接口,无需反射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆