Search code examples
c#dictionaryinterfacehashcodestrong-typing

Adapting a Class to an Interface when it implements all of the interface but doesn't declare the interface


Let the following interfaces :

interface IFoo
{
    void Foo();
}

interface IBar
{
    void Bar();
}

interface IFooBar : IFoo, IBar
{
    // No extra required feature.
}

And the class :

class Booh : IFoo, IBar
{
    public void Foo() { }
    public void Bar() { }
}

I cannot use Booh as a IFooBar, despite Booh implementing everything required by IFooBar, because it does not officially implement it.

In order to allow the use of a Booh as a IFooBar without changing Booh to class Booh : IFooBar, I have thought (based on another SO question) about writing a wrapper :

class FooBar<T> : IFooBar where T : IFoo, IBar
{
    public T Value { get; private set; }

    public FooBar(T value)
    {
        Value = value;
    }

    public void Foo() { Value.Foo(); }
    public void Bar() { Value.Bar(); }
}

The problem with that is that I can us as is !

For exemple, if I use this wrapper class as a dictionary key, it will use the reference of the wrapper instead of the reference of the wrapped object.

If I do : someDictionary.Add(new FooBar<Booh>(someBooh), whatever); and then someDictionary.Remove<Booh>(new FooBar(someBooh)); it will not remove the Booh I added in a first place, because I created two different wrappers, each of them having its own address.

To work around this, I have overriden / implemented some methods for equality checks and hash codes :

class FooBar<T> : IFooBar where T : IFoo, IBar
{
    // Same as above...

    public bool Equals(FooBar<T> other)
    {
        return Value.Equals(other.Value);
    }

    public override bool Equals(object obj)
    {
        var cast = obj as FooBar<T>;

        if (null != obj && null == cast || obj == null)
        {
            return false;
        }

        return Value.Equals(cast.Value);
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
}

This supposedly causes the wrapped object reference be used by dictionaries, I haven't tested yet.

So, my question is : are there other methods I need to override and / or implement in order to cover most (if not all) use cases ? I want that wrapper to behave like it was the object being wrapped itself, not another object. Thank you !

EDIT : Maybe I could instead make this a struct and rely on auto-boxing to wrap the wrapper struct into an object that will delegate it's hash code and equality check methods to the structure and thus use the wrapped object reference ?


Solution

  • Yes, the 3 methods you've done is all you need. A dictionary allegedly relies mostly on the hashcode.

    However your cast in Equals(object obj) will go wrong: it will cast a Booh to null. You want to test/cast both FooBar<T> and just plain T.

    JetBrains Rider offer more or less this:

        bool Equals(FooBar<T> other)
        {
            return EqualityComparer<T>.Default.Equals(Value, other.Value);
        }
    
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj is T) return Value.Equals(obj);
            if (obj.GetType() != this.GetType()) return false;
            return Equals((FooBar<T>) obj);
        }
    
        public override int GetHashCode()
        {
            return EqualityComparer<T>.Default.GetHashCode(Value);
        }
    

    Which passes these tests:

        [Fact]
        public void CanUseInDict()
        {
            var foobar= new Booh();
            IFooBar[] foobars= new IFooBar[]{ foobar.AsIFooBar() };
            Dictionary<IFooBar,string> ifoobars= new Dictionary<IFooBar, string>()
            {
                { foobar.AsIFooBar(), foobar.GetType().Name}
            };
    
            Assert.Equal( foobar.GetHashCode(),  new FooBar<Booh>( foobar ).GetHashCode());
            Assert.True( foobar.AsIFooBar().Equals( new FooBar<Booh>( foobar ) )  , "Equals FooBar<Booh>");
            Assert.True( ifoobars.ContainsKey( new FooBar<Booh>(foobar) ), "ContainsKey");            
    
            ifoobars.Remove(foobar.AsIFooBar());
            Assert.Empty(ifoobars);
        }
    

    I can't quite see that use struct instead makes much difference either way. You still have to override the Equality members the same.

    I added

    static class BoohFooBarExt
    {
        public static IFooBar AsIFooBar<T>(this T value ) where T:IFoo, IBar => new FooBar<T>(value);
    }