在没有反射的情况下实现基于类属性的接口

本文关键字:于类 属性 接口 实现 反射的 情况下 | 更新日期: 2023-09-27 18:24:04

PostSharp网站上的这个页面有以下预告片:

您将遇到的常见情况之一是需要在大量类上实现特定的接口。这可能是INotifyPropertyChangedIDisposeIEquatable或您创建的某个自定义接口。

我想写一个自定义方面,根据应用到的类的属性实现IEquatable的通用版本(最好是在编译时,而不是在运行时使用反射)。只要能够为一个简单的类添加一个属性就好了,而不必每次都实现一个自定义方法。这可能吗?我希望如此,因为它在引言中有明确的说明,但我还没有找到任何示例代码。

我在PostSharp网站上看到了这个例子,其中包括一个介绍IIdentifiable接口的例子。但它只是返回一个GUID,它与新接口添加到的类无关

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

我已经找到了一个使用T4模板的解决方案,但我想知道是否可以使用PostSharp实现同样的效果。

编辑:

需要明确的是,我希望能够写这样的东西:

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

并将其自动转换为:

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

在没有反射的情况下实现基于类属性的接口

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

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

恐怕PostSharp无法做到这一点。PostSharp在类中"注入"方面代码,但必须对方面进行编码。这里的关键是确定系统中的常见行为和交叉关注点,并将其建模为Aspects

IIdentifiable的示例中,您可以看到GUID是一个唯一的标识符,可以被系统中的许多不同类使用。这是常见的代码,是一个跨领域的问题,您发现自己在所有类实体中都在重复代码,因此Identificable可以建模为Aspect并消除重复代码。

由于不同的类有不同的Equals实现,您不能"deatach"(转换为方面)Equals的实现。等于不是一种常见的行为。平等不是贯穿各领域的问题。Equals不能是Aspect(没有反射)。