diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 08e05f16..9e38b8f9 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.1.16006")] +[assembly: AssemblyFileVersion("5.0.1.16007")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index a566da2a..20aeee22 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -90,39 +90,245 @@ namespace Newtonsoft.Json.Tests.Serialization [TestFixture] public class JsonSerializerTest : TestFixtureBase { - //[DataContract] - //public class BaseDataContract1 - //{ - // [DataMember(Name = "virtualMember")] - // public virtual string VirtualMember { get; set; } +#if !NET20 + [DataContract] + public class BaseDataContractWithHidden + { + [DataMember(Name = "virtualMember")] + public virtual string VirtualMember { get; set; } - // [DataMember(Name = "nonVirtualMember")] - // public string NonVirtualMember { get; set; } + [DataMember(Name = "nonVirtualMember")] + public string NonVirtualMember { get; set; } - // public virtual object NewMember { get; set; } - //} + public virtual object NewMember { get; set; } + } - //public class ChildDataContract1 : BaseDataContract1 - //{ - // [DataMember(Name = "NewMember")] - // public virtual new string NewMember { get; set; } - // public override string VirtualMember { get; set; } - // public string AddedMember { get; set; } - //} + public class ChildDataContractWithHidden : BaseDataContractWithHidden + { + [DataMember(Name = "NewMember")] + public virtual new string NewMember { get; set; } + public override string VirtualMember { get; set; } + public string AddedMember { get; set; } + } - //[Test] - //public void ChildDataContractTest1() - //{ - // var cc = new ChildDataContract1 - // { - // VirtualMember = "VirtualMember!", - // NonVirtualMember = "NonVirtualMember!", - // NewMember = "NewMember!" - // }; + [Test] + public void ChildDataContractTestWithHidden() + { + var cc = new ChildDataContractWithHidden + { + VirtualMember = "VirtualMember!", + NonVirtualMember = "NonVirtualMember!", + NewMember = "NewMember!" + }; - // string result = JsonConvert.SerializeObject(cc); - // Assert.AreEqual(@"{""NewMember"":""NewMember!"",""virtualMember"":""VirtualMember!"",""nonVirtualMember"":""NonVirtualMember!""}", result); - //} + string result = JsonConvert.SerializeObject(cc); + Assert.AreEqual(@"{""NewMember"":""NewMember!"",""virtualMember"":""VirtualMember!"",""nonVirtualMember"":""NonVirtualMember!""}", result); + } + + [DataContract] + public class BaseWithContract + { + [DataMember(Name = "VirtualWithDataMemberBase")] + public virtual string VirtualWithDataMember { get; set; } + [DataMember] + public virtual string Virtual { get; set; } + [DataMember(Name = "WithDataMemberBase")] + public string WithDataMember { get; set; } + [DataMember] + public string JustAProperty { get; set; } + } + + [DataContract] + public class BaseWithoutContract + { + [DataMember(Name = "VirtualWithDataMemberBase")] + public virtual string VirtualWithDataMember { get; set; } + [DataMember] + public virtual string Virtual { get; set; } + [DataMember(Name = "WithDataMemberBase")] + public string WithDataMember { get; set; } + [DataMember] + public string JustAProperty { get; set; } + } + + [DataContract] + public class SubWithoutContractNewProperties : BaseWithContract + { + [DataMember(Name = "VirtualWithDataMemberSub")] + public string VirtualWithDataMember { get; set; } + public string Virtual { get; set; } + [DataMember(Name = "WithDataMemberSub")] + public string WithDataMember { get; set; } + public string JustAProperty { get; set; } + } + + [DataContract] + public class SubWithoutContractVirtualProperties : BaseWithContract + { + public override string VirtualWithDataMember { get; set; } + [DataMember(Name = "VirtualSub")] + public override string Virtual { get; set; } + } + + [DataContract] + public class SubWithContractNewProperties : BaseWithContract + { + [DataMember(Name = "VirtualWithDataMemberSub")] + public string VirtualWithDataMember { get; set; } + [DataMember(Name = "Virtual2")] + public string Virtual { get; set; } + [DataMember(Name = "WithDataMemberSub")] + public string WithDataMember { get; set; } + [DataMember(Name = "JustAProperty2")] + public string JustAProperty { get; set; } + } + + [DataContract] + public class SubWithContractVirtualProperties : BaseWithContract + { + [DataMember(Name = "VirtualWithDataMemberSub")] + public virtual string VirtualWithDataMember { get; set; } + } + + [Test] + public void SubWithoutContractNewPropertiesTest() + { + BaseWithContract baseWith = new SubWithoutContractNewProperties + { + JustAProperty = "JustAProperty!", + Virtual = "Virtual!", + VirtualWithDataMember = "VirtualWithDataMember!", + WithDataMember = "WithDataMember!" + }; + + baseWith.JustAProperty = "JustAProperty2!"; + baseWith.Virtual = "Virtual2!"; + baseWith.VirtualWithDataMember = "VirtualWithDataMember2!"; + baseWith.WithDataMember = "WithDataMember2!"; + + string json = AssertSerializeDeserializeEqual(baseWith); + + Assert.AreEqual(@"{ + ""JustAProperty"": ""JustAProperty2!"", + ""Virtual"": ""Virtual2!"", + ""VirtualWithDataMemberBase"": ""VirtualWithDataMember2!"", + ""VirtualWithDataMemberSub"": ""VirtualWithDataMember!"", + ""WithDataMemberBase"": ""WithDataMember2!"", + ""WithDataMemberSub"": ""WithDataMember!"" +}", json); + } + + [Test] + public void SubWithoutContractVirtualPropertiesTest() + { + BaseWithContract baseWith = new SubWithoutContractVirtualProperties + { + JustAProperty = "JustAProperty!", + Virtual = "Virtual!", + VirtualWithDataMember = "VirtualWithDataMember!", + WithDataMember = "WithDataMember!" + }; + + baseWith.JustAProperty = "JustAProperty2!"; + baseWith.Virtual = "Virtual2!"; + baseWith.VirtualWithDataMember = "VirtualWithDataMember2!"; + baseWith.WithDataMember = "WithDataMember2!"; + + string json = JsonConvert.SerializeObject(baseWith, Formatting.Indented); + + Console.WriteLine(json); + + Assert.AreEqual(@"{ + ""VirtualWithDataMemberBase"": ""VirtualWithDataMember2!"", + ""VirtualSub"": ""Virtual2!"", + ""WithDataMemberBase"": ""WithDataMember2!"", + ""JustAProperty"": ""JustAProperty2!"" +}", json); + } + + [Test] + public void SubWithContractNewPropertiesTest() + { + BaseWithContract baseWith = new SubWithContractNewProperties + { + JustAProperty = "JustAProperty!", + Virtual = "Virtual!", + VirtualWithDataMember = "VirtualWithDataMember!", + WithDataMember = "WithDataMember!" + }; + + baseWith.JustAProperty = "JustAProperty2!"; + baseWith.Virtual = "Virtual2!"; + baseWith.VirtualWithDataMember = "VirtualWithDataMember2!"; + baseWith.WithDataMember = "WithDataMember2!"; + + string json = AssertSerializeDeserializeEqual(baseWith); + + Assert.AreEqual(@"{ + ""JustAProperty"": ""JustAProperty2!"", + ""JustAProperty2"": ""JustAProperty!"", + ""Virtual"": ""Virtual2!"", + ""Virtual2"": ""Virtual!"", + ""VirtualWithDataMemberBase"": ""VirtualWithDataMember2!"", + ""VirtualWithDataMemberSub"": ""VirtualWithDataMember!"", + ""WithDataMemberBase"": ""WithDataMember2!"", + ""WithDataMemberSub"": ""WithDataMember!"" +}", json); + } + + [Test] + public void SubWithContractVirtualPropertiesTest() + { + BaseWithContract baseWith = new SubWithContractVirtualProperties + { + JustAProperty = "JustAProperty!", + Virtual = "Virtual!", + VirtualWithDataMember = "VirtualWithDataMember!", + WithDataMember = "WithDataMember!" + }; + + baseWith.JustAProperty = "JustAProperty2!"; + baseWith.Virtual = "Virtual2!"; + baseWith.VirtualWithDataMember = "VirtualWithDataMember2!"; + baseWith.WithDataMember = "WithDataMember2!"; + + string json = AssertSerializeDeserializeEqual(baseWith); + + Assert.AreEqual(@"{ + ""JustAProperty"": ""JustAProperty2!"", + ""Virtual"": ""Virtual2!"", + ""VirtualWithDataMemberBase"": ""VirtualWithDataMember2!"", + ""VirtualWithDataMemberSub"": ""VirtualWithDataMember!"", + ""WithDataMemberBase"": ""WithDataMember2!"" +}", json); + } + + private string AssertSerializeDeserializeEqual(object o) + { + MemoryStream ms = new MemoryStream(); + DataContractJsonSerializer s = new DataContractJsonSerializer(o.GetType()); + s.WriteObject(ms, o); + + var data = ms.ToArray(); + JObject dataContractJson = JObject.Parse(Encoding.UTF8.GetString(data, 0, data.Length)); + dataContractJson = new JObject(dataContractJson.Properties().OrderBy(p => p.Name)); + + JObject jsonNetJson = JObject.Parse(JsonConvert.SerializeObject(o)); + jsonNetJson = new JObject(jsonNetJson.Properties().OrderBy(p => p.Name)); + + Console.WriteLine("Results for " + o.GetType().Name); + Console.WriteLine("DataContractJsonSerializer: " + dataContractJson); + Console.WriteLine("JsonDotNetSerializer : " + jsonNetJson); + + Assert.AreEqual(dataContractJson.Count, jsonNetJson.Count); + foreach (KeyValuePair property in dataContractJson) + { + Assert.IsTrue(JToken.DeepEquals(jsonNetJson[property.Key], property.Value), "Property not equal: " + property.Key); + } + + return jsonNetJson.ToString(); + } +#endif [Test] public void PersonTypedObjectDeserialization() @@ -5078,6 +5284,7 @@ To fix this error either change the environment to be fully trusted, change the }; string result = JsonConvert.SerializeObject(cc); + Console.WriteLine(result); Assert.AreEqual(@"{""differentVirtualMember"":""VirtualMember!"",""nonVirtualMember"":""NonVirtualMember!""}", result); } @@ -7018,6 +7225,134 @@ Parameter name: value", } #endif +#if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE40) + public class PopulateReadOnlyTestClass + { + public IList NonReadOnlyList { get; set; } + public IDictionary NonReadOnlyDictionary { get; set; } + + public IList Array { get; set; } + + public IList List { get; set; } + public IDictionary Dictionary { get; set; } + + public IReadOnlyCollection IReadOnlyCollection { get; set; } + public ReadOnlyCollection ReadOnlyCollection { get; set; } + public IReadOnlyList IReadOnlyList { get; set; } + + public IReadOnlyDictionary IReadOnlyDictionary { get; set; } + public ReadOnlyDictionary ReadOnlyDictionary { get; set; } + + public PopulateReadOnlyTestClass() + { + NonReadOnlyList = new List { 1 }; + NonReadOnlyDictionary = new Dictionary { { "first", 2 } }; + + Array = new[] {3}; + + List = new ReadOnlyCollection(new[] { 4 }); + Dictionary = new ReadOnlyDictionary(new Dictionary { { "first", 5 } }); + + IReadOnlyCollection = new ReadOnlyCollection(new[] { 6 }); + ReadOnlyCollection = new ReadOnlyCollection(new[] { 7 }); + IReadOnlyList = new ReadOnlyCollection(new[] { 8 }); + + IReadOnlyDictionary = new ReadOnlyDictionary(new Dictionary { { "first", 9 } }); + ReadOnlyDictionary = new ReadOnlyDictionary(new Dictionary { { "first", 10 } }); + } + } + + [Test] + public void SerializeReadOnlyCollections() + { + PopulateReadOnlyTestClass c1 = new PopulateReadOnlyTestClass(); + + string json = JsonConvert.SerializeObject(c1, Formatting.Indented); + + Assert.AreEqual(@"{ + ""NonReadOnlyList"": [ + 1 + ], + ""NonReadOnlyDictionary"": { + ""first"": 2 + }, + ""Array"": [ + 3 + ], + ""List"": [ + 4 + ], + ""Dictionary"": { + ""first"": 5 + }, + ""IReadOnlyCollection"": [ + 6 + ], + ""ReadOnlyCollection"": [ + 7 + ], + ""IReadOnlyList"": [ + 8 + ], + ""IReadOnlyDictionary"": { + ""first"": 9 + }, + ""ReadOnlyDictionary"": { + ""first"": 10 + } +}", json); + } + + [Test] + public void PopulateReadOnlyCollections() + { + string json = @"{ + ""NonReadOnlyList"": [ + 11 + ], + ""NonReadOnlyDictionary"": { + ""first"": 12 + }, + ""Array"": [ + 13 + ], + ""List"": [ + 14 + ], + ""Dictionary"": { + ""first"": 15 + }, + ""IReadOnlyCollection"": [ + 16 + ], + ""ReadOnlyCollection"": [ + 17 + ], + ""IReadOnlyList"": [ + 18 + ], + ""IReadOnlyDictionary"": { + ""first"": 19 + }, + ""ReadOnlyDictionary"": { + ""first"": 20 + } +}"; + + var c2 = JsonConvert.DeserializeObject(json); + + Assert.AreEqual(1, c2.NonReadOnlyDictionary.Count); + Assert.AreEqual(12, c2.NonReadOnlyDictionary["first"]); + + Assert.AreEqual(2, c2.NonReadOnlyList.Count); + Assert.AreEqual(1, c2.NonReadOnlyList[0]); + Assert.AreEqual(11, c2.NonReadOnlyList[1]); + + Assert.AreEqual(1, c2.Array.Count); + Assert.AreEqual(13, c2.Array[0]); + } +#endif + [Test] public void SerializeArray2D() { diff --git a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs index ea8e2887..edcc751a 100644 --- a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs +++ b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs @@ -30,6 +30,8 @@ using System.IO; #if NET20 using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Runtime.Serialization.Json; #endif using System.Text; using System.Threading; @@ -53,6 +55,18 @@ namespace Newtonsoft.Json.Tests [TestFixture] public abstract class TestFixtureBase { +#if !NET20 + protected string GetDataContractJsonSerializeResult(object o) + { + MemoryStream ms = new MemoryStream(); + DataContractJsonSerializer s = new DataContractJsonSerializer(o.GetType()); + s.WriteObject(ms, o); + + var data = ms.ToArray(); + return Encoding.UTF8.GetString(data, 0, data.Length); + } +#endif + protected string GetOffset(DateTime d, DateFormatHandling dateFormatHandling) { StringWriter sw = new StringWriter(); diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 4f8c96a8..f0e96996 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -92,6 +92,6 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.1.16006")] +[assembly: AssemblyFileVersion("5.0.1.16007")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 05258632..9d458d07 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -1025,8 +1025,8 @@ namespace Newtonsoft.Json.Serialization if (memberSerialization != MemberSerialization.OptIn) { - bool hasIgnoreDataMemberAttribute = false; - + bool hasIgnoreDataMemberAttribute = false; + #if !(NET20 || NET35) hasIgnoreDataMemberAttribute = (JsonTypeReflector.GetAttribute(attributeProvider) != null); #endif diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 8031aa14..a0edd61d 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -100,6 +100,8 @@ namespace Newtonsoft.Json.Serialization if (CollectionItemType != null) ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(underlyingType, CollectionItemType); + + IsReadOnlyOrFixedSize = ReflectionUtils.InheritsGenericDefinition(underlyingType, typeof(ReadOnlyCollection<>)); canDeserialize = true; } else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(ICollection<>), out _genericCollectionDefinitionType)) diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index f6e5978d..5d51aec4 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -89,6 +89,10 @@ namespace Newtonsoft.Json.Serialization if (ReflectionUtils.IsGenericDefinition(UnderlyingType, typeof(IDictionary<,>))) CreatedType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + +#if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE40) + IsReadOnlyOrFixedSize = ReflectionUtils.InheritsGenericDefinition(underlyingType, typeof(ReadOnlyDictionary<,>)); +#endif } #if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE40) else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(IReadOnlyDictionary<,>), out _genericCollectionDefinitionType)) diff --git a/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs b/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs index 41b953be..af4b4601 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs @@ -81,19 +81,21 @@ namespace Newtonsoft.Json.Serialization Remove(existingProperty); duplicateProperty = false; } - - if (property.DeclaringType != null && existingProperty.DeclaringType != null) + else { - if (property.DeclaringType.IsSubclassOf(existingProperty.DeclaringType)) + if (property.DeclaringType != null && existingProperty.DeclaringType != null) { - // current property is on a derived class and hides the existing - Remove(existingProperty); - duplicateProperty = false; - } - if (existingProperty.DeclaringType.IsSubclassOf(property.DeclaringType)) - { - // current property is hidden by the existing so don't add it - return; + if (property.DeclaringType.IsSubclassOf(existingProperty.DeclaringType)) + { + // current property is on a derived class and hides the existing + Remove(existingProperty); + duplicateProperty = false; + } + if (existingProperty.DeclaringType.IsSubclassOf(property.DeclaringType)) + { + // current property is hidden by the existing so don't add it + return; + } } } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index a31dc6ba..802fa4c3 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -791,7 +791,12 @@ To fix this error either change the JSON to a {1} or change the deserialized typ currentValue = property.ValueProvider.GetValue(target); gottenCurrentValue = true; - useExistingValue = (currentValue != null && !property.PropertyContract.IsReadOnlyOrFixedSize && !property.PropertyType.IsValueType()); + if (currentValue != null) + { + propertyContract = GetContractSafe(currentValue.GetType()); + + useExistingValue = (!propertyContract.IsReadOnlyOrFixedSize && !propertyContract.UnderlyingType.IsValueType()); + } } if (!property.Writable && !useExistingValue) diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 33f16b46..baf6d657 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -119,7 +119,18 @@ namespace Newtonsoft.Json.Serialization public static DataContractAttribute GetDataContractAttribute(Type type) { // DataContractAttribute does not have inheritance - return CachedAttributeGetter.GetAttribute(type); + Type currentType = type; + + while (currentType != null) + { + DataContractAttribute result = CachedAttributeGetter.GetAttribute(currentType); + if (result != null) + return result; + + currentType = currentType.BaseType(); + } + + return null; } public static DataMemberAttribute GetDataMemberAttribute(MemberInfo memberInfo) diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index a198ee43..47823fb1 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -109,6 +109,31 @@ namespace Newtonsoft.Json.Utilities return false; } + public static MethodInfo GetBaseDefinition(this PropertyInfo propertyInfo) + { + ValidationUtils.ArgumentNotNull(propertyInfo, "propertyInfo"); + + MethodInfo m = propertyInfo.GetGetMethod(); + if (m != null) + return m.GetBaseDefinition(); + + m = propertyInfo.GetSetMethod(); + if (m != null) + return m.GetBaseDefinition(); + + return null; + } + + public static bool IsPublic(PropertyInfo property) + { + if (property.GetGetMethod() != null && property.GetGetMethod().IsPublic) + return true; + if (property.GetSetMethod() != null && property.GetSetMethod().IsPublic) + return true; + + return false; + } + public static Type GetObjectType(object v) { return (v != null) ? v.GetType() : null; @@ -812,30 +837,51 @@ namespace Newtonsoft.Json.Utilities { // fix weirdness with private PropertyInfos only being returned for the current Type // find base type properties and add them to result - if ((bindingAttr & BindingFlags.NonPublic) != 0) + + // also find base properties that have been hidden by subtype properties with the same name + + while ((targetType = targetType.BaseType()) != null) { - // modify flags to not search for public fields - BindingFlags nonPublicBindingAttr = bindingAttr.RemoveFlag(BindingFlags.Public); - - while ((targetType = targetType.BaseType()) != null) + foreach (PropertyInfo propertyInfo in targetType.GetProperties(bindingAttr)) { - foreach (PropertyInfo propertyInfo in targetType.GetProperties(nonPublicBindingAttr)) - { - PropertyInfo nonPublicProperty = propertyInfo; + PropertyInfo subTypeProperty = propertyInfo; + if (!IsPublic(subTypeProperty)) + { // have to test on name rather than reference because instances are different // depending on the type that GetProperties was called on - int index = initialProperties.IndexOf(p => p.Name == nonPublicProperty.Name); + int index = initialProperties.IndexOf(p => p.Name == subTypeProperty.Name); if (index == -1) { - initialProperties.Add(nonPublicProperty); + initialProperties.Add(subTypeProperty); } else { // replace nonpublic properties for a child, but gotten from // the parent with the one from the child // the property gotten from the child will have access to private getter/setter - initialProperties[index] = nonPublicProperty; + initialProperties[index] = subTypeProperty; + } + } + else + { + if (!subTypeProperty.IsVirtual()) + { + int index = initialProperties.IndexOf(p => p.Name == subTypeProperty.Name + && p.DeclaringType == subTypeProperty.DeclaringType); + + if (index == -1) + initialProperties.Add(subTypeProperty); + } + else + { + int index = initialProperties.IndexOf(p => p.Name == subTypeProperty.Name + && p.IsVirtual() + && p.GetBaseDefinition() != null + && p.GetBaseDefinition().DeclaringType.IsAssignableFrom(subTypeProperty.DeclaringType)); + + if (index == -1) + initialProperties.Add(subTypeProperty); } } }