none
GetInterfaceMap.TargetMethods contains incorrect data RRS feed

  • General discussion

  • Hello. I'm using Type.GetInterfaceMap and its TargetMethods to satisfy my needs. However, it returns strange results and these results lead my program to bugs. See tests below.

    public interface IInterface
    {
        int Id { get; }
    }
    
    class DirectImplementation : IInterface
    {
        public int Id { get; set; }
    }
    
    class ExplicitImplementation : IInterface
    {
        int IInterface.Id { get { return 0; } }
    }
    
    class SameAssemblyType
    {
        public int Id { get; set; }
    }
    
    class IndirectIntfImplWithSameAssemblyType : SameAssemblyType, IInterface
    {
    }
    
    // AnotherAssemblyType is equal to SameAssemblyTime, but living in another Assembly
    
    public class IndirectIntfImplWithAnotherAssemblyType : AnotherAssemblyType, IInterface
    {
    }
    
    [TestClass]
    public class GetInterfaceMapTargetMethodTests
    {
        private const string IdGetterName = "get_Id";
        
        [TestMethod]
        public void DirectInterfaceImplementation_ReturnsMethodName()
        {
            var map = typeof(DirectImplementation).GetInterfaceMap(typeof(IInterface));
            var info = GetTargetMethodInfo(map);
    
            // OK, just to ensure
            Assert.AreEqual(typeof(DirectImplementation), info.DeclaringType);
            Assert.AreEqual(IdGetterName, info.Name);
        }
    
        [TestMethod]
        public void ExplicitInterfaceImplementation_ReturnsQualifiedMethodName()
        {
            var map = typeof(ExplicitImplementation).GetInterfaceMap(typeof(IInterface));
            var info = GetTargetMethodInfo(map);
    
            // OK, just to ensure
            Assert.AreEqual(typeof(ExplicitImplementation), info.DeclaringType);
            Assert.AreEqual(typeof(IInterface).FullName + "." + IdGetterName, info.Name);
        }
    
        [TestMethod]
        public void IndirectIntfImplWithSameAssemblyType_ReturnsMethodName()
        {
            var map = typeof(IndirectIntfImplWithSameAssemblyType).GetInterfaceMap(typeof(IInterface));
            var info = GetTargetMethodInfo(map);
    
            // OK: DeclaringType points to SameAssemblyType
            Assert.AreEqual(typeof(SameAssemblyType), info.DeclaringType);
            // OK: Name is "get_Id"
            Assert.AreEqual(IdGetterName, info.Name);
        }
    
        [TestMethod]
        public void IndirectIntfImplWithAnotherAssemblyType_PointsToCorrectTargetType()
        {
            var map = typeof(IndirectIntfImplWithAnotherAssemblyType).GetInterfaceMap(typeof(IInterface));
    
            // FAIL: DeclaringType points to IndirectIntfImplWithAnotherAssemblyType, but real 
            //       implementation of this method is living in AnotherAssemblyType
            Assert.AreEqual(typeof(AnotherAssemblyType), GetTargetMethodInfo(map).DeclaringType);
        }
    
        [TestMethod]
        public void IndirectIntfImplWithAnotherAssemblyType_ReturnsMethodName()
        {
            var map = typeof(IndirectIntfImplWithAnotherAssemblyType).GetInterfaceMap(typeof(IInterface));
    
            // FAIL: Name is "GetInterfaceMapTests.IInterface.get_Id", is fully-qualified. But
            //       how TargetMethod can point to non-target method?
            Assert.AreEqual(IdGetterName, GetTargetMethodInfo(map).Name);
        }
    
        private static MethodInfo GetTargetMethodInfo(InterfaceMapping map)
        {
            for (var i = 0; i < map.InterfaceMethods.Length; i++)
            {
                if (map.InterfaceMethods[i].Name.Equals(IdGetterName, StringComparison.Ordinal))
                {
                    return map.TargetMethods[i];
                }
            }
    
            throw new InvalidOperationException(
                String.Format("The type \"{0}\" does not contain method \"{1}\"", map.InterfaceType, IdGetterName));
        }
    }

    Totals: results of GetInterfaceMap.TargetMethods relied on the type's assembly. It is ok with SameAssemblyType. But when we are moving it to another assembly, method info begins to contain wrong data (incorrect DeclaringType and fully qualified Name property).

    • Changed type odinserj Wednesday, May 22, 2013 12:47 PM
    Wednesday, May 22, 2013 11:40 AM

All replies

  • I'm confused as to what problem you're having.  I took your code and put Some... in one class library.  I put the Another... stuff in another class library that references the first library and then put the unit tests in a separate assembly that referenced both.  All the tests passed.  Please identify the assemblies that each of your types are defined in.  Note that each type can only appear in a single assembly.  Based upon your comment in your tests it appears that you may be defining IInterface in multiple assemblies which won't work.  In .NET a type is always fully qualified so defining the same type in 2 different assemblies results in 2 different types.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Wednesday, May 22, 2013 5:59 PM
    Moderator
  • Thanks for reply! I'm confused too. Did you modify these classes? Problem is showing up only with base class with some method defined, interface with the same method signature, and the class, derived from base class that implements the interface. I asked my colleague to run these tests, both tests were failed.

    Here is my solution structure (sorry, I can't upload a picture):

    AnotherAssembly

    • AnotherAssemblyType.cs

    SampleAssembly

    • DirectImplementation.cs
    • ExplicitImplementation.cs
    • IInterface.cs
    • IndirectIntfImplWithAnotherAssemblyType.cs
    • IndirectIntfImplWithSameAssemblyType.cs
    • SameAssemblyType.cs

    TestsAssembly

    • GetInterfaceMapTargetMethodTests.cs

    Each source file has only one class inside. With this structure we have two failed tests.

    > Based upon your comment in your tests it appears that you may be defining IInterface in multiple assemblies which won't work.

    Nope, it is not the problem. IndirectIntfImplWithAnotherAssemblyType_PointsToCorrectTargetType test shows us the main problem. Target MethodInfo.DeclaringType points to the IndirectIntfImplWithAnotherAssemblyType class, and it says, that method 'get_Id' is defined exactly in that class. But it is wrong! Method 'get_Id' is declared in it's base class, in AnotherAssemblyType.

    But! If we move the AnotherAssemblyClass.cs file to the SampleAssembly:

    AnotherAssembly

    • <Empty>

    SampleAssembly

    • AnotherAssemblyType.cs
    • DirectImplementation.cs
    • ExplicitImplementation.cs
    • IInterface.cs
    • IndirectIntfImplWithAnotherAssemblyType.cs
    • IndirectIntfImplWithSameAssemblyType.cs
    • SameAssemblyType.cs

    TestsAssembly

    • GetInterfaceMapTargetMethodTests.cs

    Previously failed tests will pass. Target MethodInfo will point to the AnotherAssemblyType class, and it will be the correct behavior.

    Thursday, May 23, 2013 3:15 PM
  • Since some of the types you're unit testing aren't public I assume you've applied the InternalsVisibleTo attribute to your SampleAssembly.  I'm also assuming AnotherAssembly references SampleAssembly and TestsAssembly references both of the assemblies.  When I run this layout against .NET 4.0 the tests pass.  I copied the code you gave and broke them up based upon your layout. 

    IndirectIntfImplWithAnotherAssemblyType implements IInterface so if you ask for the type implementing the interface you'll get IndirectIntf... but the actual implementation is handled by the base type AnotherAssemblyType.  Therefore the interface map will correctly identify AnotherAssemblyType.get_id as the implementation of the interface method IInterface.get_id.  Hence the test should pass, and it does for me.  Moving AnotherAssembly to the SamplesAssembly has no impact because the mapping remains the same.

    You don't happen to be using the accessors stuff that unit tests used to generate automatically in VS2010 and earlier are you?  The accessors would allow you to unit test members/types that aren't public in the assembly.  But this implementation didn't behave correctly and was removed in VS2012.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Thursday, May 23, 2013 3:51 PM
    Moderator