Skip to content

Commit 6a37b6a

Browse files
optimiz3craiggwilson
authored andcommitted
Fix CSHARP-637: Fluent Lambda Expressions Don't Resolve Interfaces
Accidental regression from 5a62ca8 caused generic interface member mappings to throw. Update - added code to handle non-trivial edge case of explicit interface implementations caught during code review of JIRA-637 and rebased off of latest master changes.
1 parent 8931357 commit 6a37b6a

File tree

3 files changed

+165
-2
lines changed

3 files changed

+165
-2
lines changed

MongoDB.Bson/Serialization/BsonClassMap.cs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1562,10 +1562,42 @@ private static MemberInfo GetMemberInfoFromLambda<TMember>(Expression<Func<TClas
15621562
switch (memberInfo.MemberType)
15631563
{
15641564
case MemberTypes.Field:
1565+
memberInfo = typeof(TClass).GetField(
1566+
memberInfo.Name,
1567+
BindingFlags.Instance |
1568+
BindingFlags.Public |
1569+
BindingFlags.NonPublic);
1570+
break;
15651571
case MemberTypes.Property:
1572+
// Handle interfaces and base classes; lambdas always
1573+
// call the derived implementation.
1574+
if (memberInfo.DeclaringType != typeof(TClass))
1575+
{
1576+
var memberInfo2 = typeof(TClass).GetProperty(
1577+
memberInfo.Name,
1578+
BindingFlags.Instance |
1579+
BindingFlags.Public |
1580+
BindingFlags.NonPublic);
1581+
1582+
// Handle explicit interface implementations.
1583+
if (memberInfo2 == null &&
1584+
memberInfo.DeclaringType.IsInterface)
1585+
{
1586+
memberInfo2 = ResolveExplicitProperty(
1587+
memberInfo,
1588+
typeof(TClass));
1589+
}
1590+
1591+
memberInfo = memberInfo2;
1592+
}
15661593
break;
15671594
default:
1568-
throw new BsonSerializationException("Invalid lambda expression");
1595+
memberInfo = null;
1596+
break;
1597+
}
1598+
if (memberInfo == null)
1599+
{
1600+
throw new BsonSerializationException("Invalid lambda expression");
15691601
}
15701602
return memberInfo;
15711603
}
@@ -1574,5 +1606,56 @@ private static string GetMemberNameFromLambda<TMember>(Expression<Func<TClass, T
15741606
{
15751607
return GetMemberInfoFromLambda(memberLambda).Name;
15761608
}
1609+
1610+
private static PropertyInfo ResolveExplicitProperty(
1611+
MemberInfo interfaceMemberInfo,
1612+
Type targetType)
1613+
{
1614+
var interfaceType = interfaceMemberInfo.DeclaringType;
1615+
1616+
// An interface map must be used because because there is no
1617+
// other officially documented way to derive the explicitly
1618+
// implemented property name.
1619+
var interfaceMap = targetType.GetInterfaceMap(interfaceType);
1620+
1621+
var interfacePropertyAccessors =
1622+
((PropertyInfo)interfaceMemberInfo).GetAccessors(true);
1623+
1624+
var targetPropertyAccessors =
1625+
interfacePropertyAccessors.Select(accessor =>
1626+
{
1627+
var index = Array.IndexOf<MethodInfo>(interfaceMap.InterfaceMethods, accessor);
1628+
1629+
return interfaceMap.TargetMethods[index];
1630+
}).ToArray();
1631+
1632+
// Binding must be done by accessor methods because interface
1633+
// maps only map accessor methods and do not map properties.
1634+
return targetType.GetProperties(
1635+
BindingFlags.Instance |
1636+
BindingFlags.Public |
1637+
BindingFlags.NonPublic)
1638+
.Single(propertyInfo =>
1639+
{
1640+
var accessors = propertyInfo.GetAccessors(true);
1641+
1642+
if (accessors.Length != targetPropertyAccessors.Length)
1643+
{
1644+
return false;
1645+
}
1646+
1647+
for (var i = 0; i < accessors.Length; ++i)
1648+
{
1649+
if (Array.IndexOf<MethodInfo>(
1650+
targetPropertyAccessors,
1651+
accessors[i]) < 0)
1652+
{
1653+
return false;
1654+
}
1655+
}
1656+
1657+
return true;
1658+
});
1659+
}
15771660
}
15781661
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* Copyright 2010-2012 10gen Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using NUnit.Framework;
17+
18+
using MongoDB.Bson;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Bson.Serialization.Attributes;
21+
22+
namespace MongoDB.BsonUnitTests.Jira.CSharp637
23+
{
24+
[TestFixture]
25+
public class CSharp637Tests
26+
{
27+
public interface IMyInterface
28+
{
29+
string SomeField1 { get; set; }
30+
31+
string SomeField2 { get; set; }
32+
}
33+
34+
public class MyClass : IMyInterface
35+
{
36+
[BsonElement("foo")]
37+
public string SomeField1 { get; set; }
38+
39+
[BsonElement("bar")]
40+
string IMyInterface.SomeField2 { get; set; }
41+
}
42+
43+
public static BsonMemberMap MapMyInterface1<T>()
44+
where T : IMyInterface
45+
{
46+
var classMap = new BsonClassMap<T>();
47+
classMap.AutoMap();
48+
return classMap.MapMember(t => t.SomeField1);
49+
}
50+
51+
public static BsonMemberMap MapMyInterface2<T>()
52+
where T : IMyInterface
53+
{
54+
var classMap = new BsonClassMap<T>();
55+
classMap.AutoMap();
56+
return classMap.MapMember(t => t.SomeField2);
57+
}
58+
59+
[Test]
60+
public void TestMapGenericMember()
61+
{
62+
var memberMap = MapMyInterface1<MyClass>();
63+
Assert.IsNotNull(memberMap);
64+
Assert.AreEqual(typeof(MyClass), memberMap.ClassMap.ClassType);
65+
Assert.AreEqual("foo", memberMap.ElementName);
66+
Assert.AreEqual("SomeField1", memberMap.MemberName);
67+
}
68+
69+
[Test]
70+
public void TestMapExplicitGenericMember()
71+
{
72+
var memberMap = MapMyInterface2<MyClass>();
73+
Assert.IsNotNull(memberMap);
74+
Assert.AreEqual(typeof(MyClass), memberMap.ClassMap.ClassType);
75+
Assert.AreEqual("bar", memberMap.ElementName);
76+
Assert.AreEqual("MongoDB.BsonUnitTests.Jira.CSharp637.CSharp637Tests.IMyInterface.SomeField2", memberMap.MemberName);
77+
}
78+
}
79+
}

MongoDB.BsonUnitTests/MongoDB.BsonUnitTests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<Compile Include="BsonUtilsTests.cs" />
8484
<Compile Include="Jira\CSharp476Tests.cs" />
8585
<Compile Include="Jira\CSharp624Tests.cs" />
86+
<Compile Include="Jira\CSharp637Tests.cs" />
8687
<Compile Include="Jira\CSharp648Tests.cs" />
8788
<Compile Include="Serialization\Attributes\BsonRepresentationAttributeTests.cs" />
8889
<Compile Include="Serialization\BsonClassMapAutoMappingTests.cs" />
@@ -240,4 +241,4 @@
240241
<Target Name="AfterBuild">
241242
</Target>
242243
-->
243-
</Project>
244+
</Project>

0 commit comments

Comments
 (0)