Adapter/Wrapper 和相等的引用

Adapter/Wrapper and equal references

我想为另一种类型创建包装器 class。这可以正常工作,直到(引用)相等的对象需要具有(引用)相等的包装器为止。

一个例子:

public interface ITest<T>
{
    T GetInstance(bool createNew);
}

public class Test : ITest<Test>
{
    private static Test instance;

    public Test GetInstance(bool createNew)
    {
        if (instance == null || createNew)
        {
            instance = new Test();
        }
        return instance;
    }
}


public class TestWrapper : ITest<TestWrapper>
{
    private readonly Test wrapped;
    public TestWrapper(Test wrapped)
    {
        this.wrapped = wrapped;
    }

    public TestWrapper GetInstance(bool createNew)
    {
        return new TestWrapper(wrapped.GetInstance(createNew));
    }
}

Test.GetInstancereturns总是同一个实例,只要参数createNewfalse。 相比之下 TestWrapper.GetInstance returns 总是一个新实例。

因为我希望能够用 TestWrapper 替换 Test,所以我搜索了一个解决方案,以便在最后,包装器 returns 只有一个新实例,如果测试returns 一个新实例。但是,TestWrapper 不应该了解 Test 的内部结构。

测试代码为

private static void RunTest<T>(ITest<T> cls)
{
    var i1 = (ITest<T>)cls.GetInstance(false);
    var i2 = (ITest<T>)cls.GetInstance(false);
    var i3 = (ITest<T>)cls.GetInstance(true);

    var dic = new Dictionary<ITest<T>, bool>();
    if (!dic.ContainsKey(i1)) dic.Add(i1, false); else dic[i1] = true;
    if (!dic.ContainsKey(i2)) dic.Add(i2, false); else dic[i2] = true;
    if (!dic.ContainsKey(i3)) dic.Add(i3, false); else dic[i3] = true;

    Console.WriteLine(string.Join(", ", dic.Select(a => a.Value.ToString())));
}

想要的结果是

True, False

这就是将 new Test() 传递给该方法后得到的结果。 如果您通过 new TestWrapper(new Test()),您将获得

False, False, False

有一个基于简单缓存的解决方案 (Dictionary<Test, TestWrapper>) - 但是有了这个,我会在内存中保存许多实例而不进一步使用它们(并且 GC 无法收集这些实例,因为有参考文献)。

我玩了一下 WeakReferences,但我找不到可以用来存储 WeakReference 的密钥 - 因此我必须遍历缓存列表并搜索正确的实例是缓慢的。此外,我必须为每个成员(有自己的缓存)实现这个解决方案,这似乎不是一个很好的解决方案...

我希望我已经充分解释了我的问题 ;) 所以,我的问题是:

我无法访问 Test class,并且只能有限地访问使用它的代码(只要它实现了接口,我就可以传递任意实例)

不,你不能作弊object.ReferenceEquals()。但是,object.ReferenceEquals() 被有意使用 很少 ,并且通常在确实需要引用相等的情况下使用。

The runtime need it in order to get things right. E.g. if the instance is used as a key in an Dictionary<>

实际上,运行时通常使用单个对象的 .GetHashCode().Equals() 行为,但如果您不在 class es,这些方法的基本 System.Object 实现默认依赖于对象引用。

因此,如果您能够更改 Test class 和 TestWrapper class 的代码,则可以在那些 classes 以确保他们将等效对象视为相等。

另一种(通常更好的)方法是创建一个 IEqualityComparer<> 实现以用于您的特定用例。您在 Dictionary<> 中提到了键:您可以在创建字典时向字典提供一个 IEqualityComparer<> 实例,以使其完全按照您想要的方式测试是否相等。

var dict = new Dictionary<object, object(new TestsAndWrappersAreEqualComparer());
var test = Test.GetInstance(true);
var testWrapper = TestWrapper.GetInstance(true);
dict[test] = test;
Console.WriteLine(dict.ContainsKey(test)); // true