diff --git a/Realm.PCL/Realm.PCL.csproj b/Realm.PCL/Realm.PCL.csproj index cc8781d1cf..bcbbf2e0cd 100644 --- a/Realm.PCL/Realm.PCL.csproj +++ b/Realm.PCL/Realm.PCL.csproj @@ -97,9 +97,6 @@ PreserveAttribute.cs - - ICopyValuesFrom.cs - Exceptions\RealmInvalidObjectException.cs diff --git a/Realm.PCL/RealmListPCL.cs b/Realm.PCL/RealmListPCL.cs index 524302ddeb..3edefc97ec 100755 --- a/Realm.PCL/RealmListPCL.cs +++ b/Realm.PCL/RealmListPCL.cs @@ -40,7 +40,7 @@ namespace Realms /// /// /// Type of the RealmObject which is the target of the relationship. - public class RealmList : IList, IRealmList, IDynamicMetaObjectProvider, ICopyValuesFrom where T : RealmObject + public class RealmList : IList, IRealmList, IDynamicMetaObjectProvider where T : RealmObject { /// @@ -227,11 +227,6 @@ private void ManageObjectIfNeeded(T obj) #endregion - void ICopyValuesFrom.CopyValuesFrom(IEnumerable values) - { - RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); - } - DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) { RealmPCLHelpers.ThrowProxyShouldNeverBeUsed(); diff --git a/Realm.Shared/Attributes.cs b/Realm.Shared/Attributes.cs index e72e366612..76fffd73c4 100644 --- a/Realm.Shared/Attributes.cs +++ b/Realm.Shared/Attributes.cs @@ -60,11 +60,5 @@ public WovenAttribute(Type helperType) [AttributeUsage(AttributeTargets.Property)] public class WovenPropertyAttribute : Attribute { - internal string BackingFieldName { get; private set; } - - public WovenPropertyAttribute(string backingFieldName) - { - this.BackingFieldName = backingFieldName; - } } } diff --git a/Realm.Shared/Dynamic/DynamicRealmObjectHelper.cs b/Realm.Shared/Dynamic/DynamicRealmObjectHelper.cs index b4d1e9936c..e8fb7f6811 100644 --- a/Realm.Shared/Dynamic/DynamicRealmObjectHelper.cs +++ b/Realm.Shared/Dynamic/DynamicRealmObjectHelper.cs @@ -17,6 +17,8 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Collections.Generic; +using System.Reflection; using Realms.Weaving; namespace Realms.Dynamic @@ -25,6 +27,11 @@ internal class DynamicRealmObjectHelper : IRealmObjectHelper { internal static readonly DynamicRealmObjectHelper Instance = new DynamicRealmObjectHelper(); + public void CopyToRealm(RealmObject instance) + { + throw new NotSupportedException("DynamicRealmObjectHelper cannot exist in unmanaged state, so CopyToRealm should not be called ever."); + } + public RealmObject CreateInstance() { return new DynamicRealmObject(); diff --git a/Realm.Shared/ICopyValuesFrom.cs b/Realm.Shared/ICopyValuesFrom.cs deleted file mode 100644 index a14f52a427..0000000000 --- a/Realm.Shared/ICopyValuesFrom.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -namespace Realms -{ - - // Interface allows us to cast a generic RealmList to this and invoke CopyValuesFrom - internal interface ICopyValuesFrom - { - void CopyValuesFrom(IEnumerable values); - } -} - diff --git a/Realm.Shared/Realm.Shared.projitems b/Realm.Shared/Realm.Shared.projitems index 07a02e8ee8..14a24309ee 100644 --- a/Realm.Shared/Realm.Shared.projitems +++ b/Realm.Shared/Realm.Shared.projitems @@ -57,7 +57,6 @@ - diff --git a/Realm.Shared/RealmList.cs b/Realm.Shared/RealmList.cs index e58449dc17..77f18c0244 100755 --- a/Realm.Shared/RealmList.cs +++ b/Realm.Shared/RealmList.cs @@ -38,7 +38,7 @@ namespace Realms /// /// Type of the RealmObject which is the target of the relationship. [Preserve(AllMembers = true)] - public class RealmList : IList, IRealmList, IDynamicMetaObjectProvider, ICopyValuesFrom where T : RealmObject + public class RealmList : IList, IRealmList, IDynamicMetaObjectProvider where T : RealmObject { public class Enumerator : IEnumerator { @@ -312,14 +312,6 @@ private void ManageObjectIfNeeded(T obj) #endregion DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression expression) => new Dynamic.MetaRealmList(expression, this); - - void ICopyValuesFrom.CopyValuesFrom(IEnumerable values) - { - foreach (var item in values.Cast()) - { - Add(item); - } - } } [Preserve(AllMembers = true)] diff --git a/Realm.Shared/RealmObject.cs b/Realm.Shared/RealmObject.cs index ef02b287b5..0fbb56288f 100644 --- a/Realm.Shared/RealmObject.cs +++ b/Realm.Shared/RealmObject.cs @@ -88,28 +88,7 @@ internal class Metadata internal void _CopyDataFromBackingFieldsToRow() { Debug.Assert(this.IsManaged); - - foreach (var property in _metadata.Schema) - { - var field = property.PropertyInfo.DeclaringType.GetField( - property.PropertyInfo.GetCustomAttribute().BackingFieldName, - BindingFlags.Instance | BindingFlags.NonPublic - ); - var value = field?.GetValue(this); - if (value != null) { - var listValue = value as IEnumerable; - if (listValue != null) // assume it is IList NOT a RealmList so need to wipe afer copy - { - // cope with ReplaceListGetter creating a getter which assumes - // a backing field for a managed IList is already a RealmList, so null it first - field.SetValue(this, null); // now getter will create a RealmList below - var realmList = (ICopyValuesFrom)property.PropertyInfo.GetValue(this, null); - realmList.CopyValuesFrom(listValue); - } else { - property.PropertyInfo.SetValue(this, value, null); - } - } // only null if blank relationship or string so leave as default - } + _metadata.Helper.CopyToRealm(this); } #region Getters diff --git a/Realm.Shared/weaving/IRealmObjectHelper.cs b/Realm.Shared/weaving/IRealmObjectHelper.cs index 43e7a1def9..c14950f5cd 100644 --- a/Realm.Shared/weaving/IRealmObjectHelper.cs +++ b/Realm.Shared/weaving/IRealmObjectHelper.cs @@ -16,13 +16,12 @@ // //////////////////////////////////////////////////////////////////////////// -using System; - namespace Realms.Weaving { public interface IRealmObjectHelper { RealmObject CreateInstance(); - } -} + void CopyToRealm(RealmObject instance); + } +} \ No newline at end of file diff --git a/RealmWeaver/ModuleWeaver.cs b/RealmWeaver/ModuleWeaver.cs index bf7536fa8d..33108932d6 100644 --- a/RealmWeaver/ModuleWeaver.cs +++ b/RealmWeaver/ModuleWeaver.cs @@ -15,7 +15,7 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////// - + using System; using System.Linq; using Mono.Cecil; @@ -48,6 +48,9 @@ public class ModuleWeaver private TypeReference System_String; private TypeReference System_Type; private TypeReference System_IList; + private TypeReference System_DateTimeOffset; + private TypeReference System_Int32; + private MethodReference System_DatetimeOffset_Op_Inequality; private MethodReference _propChangedEventArgsConstructor; private MethodReference _propChangedEventHandlerInvokeReference; @@ -167,17 +170,19 @@ public void Execute() _wovenPropertyAttributeConstructor = ModuleDefinition.ImportReference(wovenPropertyAttributeClass.GetConstructors().First()); _corLib = AssemblyResolver.Resolve((AssemblyNameReference)ModuleDefinition.TypeSystem.CoreLibrary); - System_Object = ModuleDefinition.TypeSystem.Object; + System_Object = ModuleDefinition.TypeSystem.Object; System_Boolean = ModuleDefinition.TypeSystem.Boolean; System_String = ModuleDefinition.TypeSystem.String; - var typeTypeDefinition = _corLib.MainModule.GetType("System.Type"); - if (typeTypeDefinition == null) // For PCL's System.Type is only accessible as an ExportedType for some reason. - { - typeTypeDefinition = _corLib.MainModule.ExportedTypes.First(t => t.FullName == "System.Type").Resolve(); - } - System_Type = ModuleDefinition.ImportReference(typeTypeDefinition); + System_Int32 = ModuleDefinition.TypeSystem.Int32; + + var dateTimeOffsetType = GetTypeFromSystemAssembly("System.DateTimeOffset"); + System_DateTimeOffset = ModuleDefinition.ImportReference(dateTimeOffsetType); + System_DatetimeOffset_Op_Inequality = ModuleDefinition.ImportReference(dateTimeOffsetType.GetMethods().Single(m => m.Name == "op_Inequality")); + + System_Type = ModuleDefinition.ImportReference(GetTypeFromSystemAssembly("System.Type")); var listTypeDefinition = _corLib.MainModule.GetType("System.Collections.Generic.List`1"); + if (listTypeDefinition == null) { System_IList = ModuleDefinition.ImportReference(typeof(System.Collections.Generic.List<>)); @@ -214,7 +219,7 @@ public void Execute() } catch (Exception e) { - LogError( $"Unexpected error caught weaving type '{type.Name}': {e.Message}.\r\nCallstack:\r\n{e.StackTrace}"); + LogError($"Unexpected error caught weaving type '{type.Name}': {e.Message}.\r\nCallstack:\r\n{e.StackTrace}"); } } @@ -227,7 +232,7 @@ private void WeaveType(TypeDefinition type, Dictionary t.FullName == "System.ComponentModel.INotifyPropertyChanged"); - + EventDefinition propChangedEventDefinition = null; FieldDefinition propChangedFieldDefinition = null; @@ -237,13 +242,17 @@ private void WeaveType(TypeDefinition type, Dictionary X.FullName.EndsWith("::PropertyChanged")); } - var persistedPropertyCount = 0; - foreach (var prop in type.Properties.Where( x => x.HasThis && !x.CustomAttributes.Any(a => a.AttributeType.Name == "IgnoredAttribute"))) + var persistedProperties = new Dictionary(); + foreach (var prop in type.Properties.Where(x => x.HasThis && !x.CustomAttributes.Any(a => a.AttributeType.Name == "IgnoredAttribute"))) { try { - var wasWoven = WeaveProperty(prop, type, methodTable, typeImplementsPropertyChanged, propChangedEventDefinition, propChangedFieldDefinition); - if (wasWoven) persistedPropertyCount++; + FieldReference field; + var wasWoven = WeaveProperty(prop, type, methodTable, typeImplementsPropertyChanged, propChangedEventDefinition, propChangedFieldDefinition, out field); + if (wasWoven) + { + persistedProperties[prop] = field; + } } catch (Exception e) { @@ -254,7 +263,7 @@ private void WeaveType(TypeDefinition type, Dictionary> methodTable, - bool typeImplementsPropertyChanged, EventDefinition propChangedEventDefinition, - FieldDefinition propChangedFieldDefinition) + bool typeImplementsPropertyChanged, EventDefinition propChangedEventDefinition, + FieldDefinition propChangedFieldDefinition, out FieldReference backingField) { var sequencePoint = prop.GetMethod.Body.Instructions.First().SequencePoint; var columnName = prop.Name; var mapToAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "MapToAttribute"); if (mapToAttribute != null) - columnName = ((string) mapToAttribute.ConstructorArguments[0].Value); + columnName = ((string)mapToAttribute.ConstructorArguments[0].Value); - var backingField = GetBackingField(prop); + backingField = GetBackingField(prop); var isIndexed = prop.CustomAttributes.Any(a => a.AttributeType.Name == "IndexedAttribute"); if (isIndexed && (!_indexableTypes.Contains(prop.PropertyType.FullName))) { @@ -316,7 +325,7 @@ private bool WeaveProperty(PropertyDefinition prop, TypeDefinition type, Diction return false; } - if (!prop.IsAutomatic()) + if (!prop.IsAutomatic()) { if (prop.PropertyType.Resolve().BaseType.IsSameAs(_realmObject)) LogWarningPoint( @@ -329,7 +338,7 @@ private bool WeaveProperty(PropertyDefinition prop, TypeDefinition type, Diction // If the property is automatic but doesn't have a setter, we should still ignore it. if (prop.SetMethod == null) return false; - + var typeId = prop.PropertyType.FullName + (isPrimaryKey ? " unique" : ""); if (!methodTable.ContainsKey(typeId)) { @@ -345,7 +354,7 @@ private bool WeaveProperty(PropertyDefinition prop, TypeDefinition type, Diction // treat IList and RealmList similarly but IList gets a default so is useable as standalone // IList or RealmList allows people to declare lists only of _realmObject due to the class definition - else if (prop.PropertyType.Name == "IList`1" && prop.PropertyType.Namespace == "System.Collections.Generic") + else if (IsIList(prop)) { var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); if (!elementType.Resolve().BaseType.IsSameAs(_realmObject)) @@ -377,7 +386,7 @@ private bool WeaveProperty(PropertyDefinition prop, TypeDefinition type, Diction new GenericInstanceMethod(_genericGetListValueReference) { GenericArguments = { elementType } }, elementType, ModuleDefinition.ImportReference(concreteListConstructor)); } - else if (prop.PropertyType.Name == "RealmList`1" && prop.PropertyType.Namespace == "Realms") + else if (IsRealmList(prop)) { var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); if (prop.SetMethod != null) @@ -402,11 +411,11 @@ private bool WeaveProperty(PropertyDefinition prop, TypeDefinition type, Diction } ReplaceGetter(prop, columnName, - new GenericInstanceMethod(_genericGetObjectValueReference) {GenericArguments = {prop.PropertyType}}); + new GenericInstanceMethod(_genericGetObjectValueReference) { GenericArguments = { prop.PropertyType } }); ReplaceSetter(prop, backingField, columnName, - new GenericInstanceMethod(_genericSetObjectValueReference) {GenericArguments = {prop.PropertyType}}, + new GenericInstanceMethod(_genericSetObjectValueReference) { GenericArguments = { prop.PropertyType } }, typeImplementsPropertyChanged, propChangedEventDefinition, propChangedFieldDefinition); - // with casting in the _realmObject methods, should just work + // with casting in the _realmObject methods, should just work } else if (prop.PropertyType.FullName == "System.DateTime") { @@ -425,7 +434,6 @@ private bool WeaveProperty(PropertyDefinition prop, TypeDefinition type, Diction prop.CustomAttributes.Add(preserveAttribute); var wovenPropertyAttribute = new CustomAttribute(_wovenPropertyAttributeConstructor); - wovenPropertyAttribute.ConstructorArguments.Add(new CustomAttributeArgument(System_String, backingField.Name)); prop.CustomAttributes.Add(wovenPropertyAttribute); Debug.WriteLine(""); @@ -634,7 +642,7 @@ void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, string if (setValueReference == null) throw new ArgumentNullException(nameof(setValueReference)); - + if (!weavePropertyChanged) { var start = prop.SetMethod.Body.Instructions.First(); @@ -697,12 +705,12 @@ void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, string ldloc.1 brfalse.s IL_0017 */ - il.Append(il.Create(OpCodes.Ldarg_0)); + il.Append(il.Create(OpCodes.Ldarg_0)); il.Append(il.Create(OpCodes.Call, _realmObjectIsManagedGetter)); - il.Append(il.Create(OpCodes.Ldc_I4_0)); - il.Append(il.Create(OpCodes.Ceq)); - il.Append(il.Create(OpCodes.Stloc_1)); - il.Append(il.Create(OpCodes.Ldloc_1)); + il.Append(il.Create(OpCodes.Ldc_I4_0)); + il.Append(il.Create(OpCodes.Ceq)); + il.Append(il.Create(OpCodes.Stloc_1)); + il.Append(il.Create(OpCodes.Ldloc_1)); var jumpToLabelA = il.Create(OpCodes.Nop); il.Append(jumpToLabelA); // Jump to A @@ -790,7 +798,94 @@ private static FieldReference GetBackingField(PropertyDefinition property) .SingleOrDefault(); } - private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, MethodDefinition objectConstructor) + private static bool IsIList(PropertyDefinition property) + { + return property.PropertyType.Name == "IList`1" && property.PropertyType.Namespace == "System.Collections.Generic"; + } + + private static bool IsRealmList(PropertyDefinition property) + { + return property.PropertyType.Name == "RealmList`1" && property.PropertyType.Namespace == "Realms"; + } + + private static bool IsDateTimeOffset(PropertyDefinition property) + { + return property.PropertyType.Name == "DateTimeOffset" && property.PropertyType.Namespace == "System"; + } + + private static bool IsNullable(PropertyDefinition property) + { + return property.PropertyType.Name.Contains("Nullable`1") && property.PropertyType.Namespace == "System"; + } + + private static bool IsSingle(PropertyDefinition property) + { + return property.PropertyType.Name == "Single" && property.PropertyType.Namespace == "System"; + } + + private static bool IsDouble(PropertyDefinition property) + { + return property.PropertyType.Name == "Double" && property.PropertyType.Namespace == "System"; + } + + private MethodReference GetIListMethodReference(TypeDefinition interfaceType, string methodName, GenericInstanceType genericInstance) + { + MethodReference reference = null; + var definition = interfaceType.GetMethods().FirstOrDefault(m => m.FullName.Contains($"::{methodName}")); + if (definition == null) + { + foreach (var parent in interfaceType.Interfaces) + { + reference = GetIListMethodReference(parent.Resolve(), methodName, genericInstance); + if (reference != null) + { + break; + } + } + } + else + { + var generic = definition.MakeHostInstanceGeneric(genericInstance.GenericArguments.ToArray()); + reference = ModuleDefinition.ImportReference(generic); + } + + return reference; + } + + private MethodReference MakeMethodGeneric(MethodReference methodReference, GenericInstanceType genericInstance) + { + var reference = new MethodReference(methodReference.Name, methodReference.ReturnType, genericInstance) + { + HasThis = methodReference.HasThis, + ExplicitThis = methodReference.ExplicitThis, + CallingConvention = methodReference.CallingConvention + }; + + foreach (var parameter in methodReference.Parameters) + { + reference.Parameters.Add(parameter); + } + + foreach (var genericParameter in methodReference.GenericParameters) + { + reference.GenericParameters.Add(new GenericParameter(genericParameter.Name, reference)); + } + + return reference; + } + + private TypeDefinition GetTypeFromSystemAssembly(string typeName) + { + var objectTypeDefinition = _corLib.MainModule.GetType(typeName); + if (objectTypeDefinition == null) // For PCL's System.XXX is only accessible as an ExportedType for some reason. + { + objectTypeDefinition = _corLib.MainModule.ExportedTypes.First(t => t.FullName == typeName).Resolve(); + } + + return objectTypeDefinition; + } + + private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, MethodDefinition objectConstructor, IDictionary properties) { var helperType = new TypeDefinition(null, "RealmHelper", TypeAttributes.Class | TypeAttributes.NestedPrivate | TypeAttributes.BeforeFieldInit, @@ -806,6 +901,168 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me } helperType.Methods.Add(createInstance); + var copyToRealm = new MethodDefinition("CopyToRealm", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, ModuleDefinition.TypeSystem.Void); + { + // This roughly translates to + /* + var castInstance = (ObjectType)instance; + + *foreach* non-list woven property in castInstance's schema + if (castInstance.field != default(fieldType)) + { + castInstance.Property = castInstance.Field; + } + + *foreach* list woven property in castInstance's schema + var list = castInstance.field; + castInstance.field = null; + for (var i = 0; i < list.Count; i++) + { + castInstance.Property.Add(list[i]); + } + */ + + var instanceParameter = new ParameterDefinition("instance", ParameterAttributes.None, ModuleDefinition.ImportReference(_realmObject)); + copyToRealm.Parameters.Add(instanceParameter); + + copyToRealm.Body.Variables.Add(new VariableDefinition(ModuleDefinition.ImportReference(realmObjectType))); + + byte currentStloc = 1; + if (properties.Any(p => IsDateTimeOffset(p.Key))) + { + copyToRealm.Body.Variables.Add(new VariableDefinition(System_DateTimeOffset)); + currentStloc++; + } + + foreach (var kvp in properties.Where(kvp => IsIList(kvp.Key) || IsRealmList(kvp.Key))) + { + copyToRealm.Body.Variables.Add(new VariableDefinition(ModuleDefinition.ImportReference(kvp.Value.FieldType))); + copyToRealm.Body.Variables.Add(new VariableDefinition(System_Int32)); + } + + var il = copyToRealm.Body.GetILProcessor(); + il.Append(il.Create(OpCodes.Ldarg_1)); + il.Append(il.Create(OpCodes.Castclass, ModuleDefinition.ImportReference(realmObjectType))); + il.Append(il.Create(OpCodes.Stloc_0)); + + foreach (var kvp in properties) + { + var property = kvp.Key; + var field = kvp.Value; + + if (property.SetMethod != null) + { + if (!IsNullable(property)) + { + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Ldfld, field)); + + if (IsDateTimeOffset(property)) + { + il.Append(il.Create(OpCodes.Ldloca_S, (byte)1)); + il.Append(il.Create(OpCodes.Initobj, field.FieldType)); + il.Append(il.Create(OpCodes.Ldloc_1)); + il.Append(il.Create(OpCodes.Call, System_DatetimeOffset_Op_Inequality)); + } + else if (IsSingle(property)) + { + il.Append(il.Create(OpCodes.Ldc_R4, 0f)); + } + else if (IsDouble(property)) + { + il.Append(il.Create(OpCodes.Ldc_R8, 0.0)); + } + } + + var jumpLabel = il.Create(OpCodes.Nop); + il.Append(jumpLabel); + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Ldfld, field)); + il.Append(il.Create(OpCodes.Call, ModuleDefinition.ImportReference(property.SetMethod))); + var label = il.Create(OpCodes.Nop); + il.Append(label); + + if (!IsNullable(property)) + { + if (IsSingle(property) || IsDouble(property)) + { + il.Replace(jumpLabel, il.Create(OpCodes.Beq_S, label)); + } + else + { + il.Replace(jumpLabel, il.Create(OpCodes.Brfalse_S, label)); + } + } + } + else if (IsIList(property) || IsRealmList(property)) + { + var propertyTypeDefinition = property.PropertyType.Resolve(); + var genericType = (GenericInstanceType)property.PropertyType; + var iList_Get_ItemMethodReference = GetIListMethodReference(propertyTypeDefinition, "get_Item", genericType); + var iList_AddMethodReference = GetIListMethodReference(propertyTypeDefinition, "Add", genericType); + var iList_Get_CountMethodReference = GetIListMethodReference(propertyTypeDefinition, "get_Count", genericType); + + var iteratorStLoc = (byte)(currentStloc + 1); + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Ldfld, field)); + + var jumpPlaceholder = il.Create(OpCodes.Nop); + il.Append(jumpPlaceholder); + + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Ldfld, field)); + il.Append(il.Create(OpCodes.Stloc_S, currentStloc)); + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Ldnull)); + il.Append(il.Create(OpCodes.Stfld, field)); + il.Append(il.Create(OpCodes.Ldc_I4_0)); + il.Append(il.Create(OpCodes.Stloc_S, iteratorStLoc)); + + var cyclePlaceholder = il.Create(OpCodes.Nop); + il.Append(cyclePlaceholder); + + var cycleStart = il.Create(OpCodes.Nop); + il.Append(cycleStart); + + il.Append(il.Create(OpCodes.Ldloc_0)); + il.Append(il.Create(OpCodes.Callvirt, ModuleDefinition.ImportReference(property.GetMethod))); + il.Append(il.Create(OpCodes.Ldloc_S, currentStloc)); + il.Append(il.Create(OpCodes.Ldloc_S, iteratorStLoc)); + il.Append(il.Create(OpCodes.Callvirt, iList_Get_ItemMethodReference)); + il.Append(il.Create(OpCodes.Callvirt, iList_AddMethodReference)); + il.Append(il.Create(OpCodes.Ldloc_S, iteratorStLoc)); + il.Append(il.Create(OpCodes.Ldc_I4_1)); + il.Append(il.Create(OpCodes.Add)); + il.Append(il.Create(OpCodes.Stloc_S, iteratorStLoc)); + + var cycleLabel = il.Create(OpCodes.Nop); + il.Append(cycleLabel); + il.Replace(cyclePlaceholder, il.Create(OpCodes.Br_S, cycleLabel)); + + il.Append(il.Create(OpCodes.Ldloc_S, iteratorStLoc)); + il.Append(il.Create(OpCodes.Ldloc_S, currentStloc)); + il.Append(il.Create(OpCodes.Callvirt, iList_Get_CountMethodReference)); + il.Append(il.Create(OpCodes.Blt_S, cycleStart)); + + var jumpLabel = il.Create(OpCodes.Nop); + il.Append(jumpLabel); + il.Replace(jumpPlaceholder, il.Create(OpCodes.Brfalse_S, jumpLabel)); + + currentStloc += 2; + } + else + { + var sequencePoint = property.GetMethod.Body.Instructions.First().SequencePoint; + LogErrorPoint($"{realmObjectType.Name}.{property.Name} does not have a setter and is not an IList. This is an error in Realm, so please file a bug report.", sequencePoint); + } + } + + il.Emit(OpCodes.Ret); + } + copyToRealm.CustomAttributes.Add(new CustomAttribute(_preserveAttributeConstructor)); + helperType.Methods.Add(copyToRealm); + const MethodAttributes ctorAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; var ctor = new MethodDefinition(".ctor", ctorAttributes, ModuleDefinition.TypeSystem.Void); { diff --git a/Tests/IntegrationTests.Shared/PerformanceTests.cs b/Tests/IntegrationTests.Shared/PerformanceTests.cs index 845ca9305d..6fa96dcf71 100644 --- a/Tests/IntegrationTests.Shared/PerformanceTests.cs +++ b/Tests/IntegrationTests.Shared/PerformanceTests.cs @@ -15,14 +15,10 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////// - + using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; using Realms; @@ -132,6 +128,92 @@ public void BindingSetValuePerformanceTest(int count) Console.WriteLine("Time spent: " + sw.Elapsed); Console.WriteLine("Kilo-iterations per second: {0:0.00}", ((count/1000) / sw.Elapsed.TotalSeconds)); } + + [TestCase(100000), Explicit] + public void ManageSmallObjectPerformanceTest(int count) + { + var objects = new List(); + for (var i = 0; i < count; i++) + { + objects.Add(new MiniPerson + { + Name = "Name" + i, + IsInteresting = true + }); + } + + var sw = new Stopwatch(); + sw.Start(); + + _realm.Write(() => + { + foreach (var obj in objects) + { + _realm.Manage(obj); + } + }); + sw.Stop(); + Console.WriteLine($"{count} objects managed for {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + _realm.Write(() => + { + for (var i = 0; i < count; i++) + { + var newObject = _realm.CreateObject(); + newObject.Name = objects[i].Name; + newObject.IsInteresting = objects[i].IsInteresting; + } + }); + + Console.WriteLine($"{count} objects created for {sw.ElapsedMilliseconds} ms"); + } + + [TestCase(100000), Explicit] + public void ManageLargeObjectPerformanceTest(int count) + { + var objects = new List(); + for (var i = 0; i < count; i++) + { + objects.Add(new Person + { + FirstName = "Name" + i, + IsInteresting = true + }); + } + + var sw = new Stopwatch(); + sw.Start(); + + _realm.Write(() => + { + foreach (var obj in objects) + { + _realm.Manage(obj); + } + }); + sw.Stop(); + Console.WriteLine($"{count} objects managed for {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + _realm.Write(() => + { + for (var i = 0; i < count; i++) + { + var newObject = _realm.CreateObject(); + newObject.FirstName = objects[i].FirstName; + newObject.IsInteresting = objects[i].IsInteresting; + } + }); + + Console.WriteLine($"{count} objects created for {sw.ElapsedMilliseconds} ms"); + } } -} + public class MiniPerson : RealmObject + { + public string Name { get; set; } + + public bool IsInteresting { get; set; } + } +} \ No newline at end of file diff --git a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs index d907de9832..0e772141cc 100644 --- a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs +++ b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs @@ -18,11 +18,9 @@ using NUnit.Framework; using Realms; -using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; +using System; namespace IntegrationTests.Shared { @@ -85,5 +83,119 @@ public void RealmObject_WhenStandalone_ShouldHaveDefaultEqualsImplementation() Assert.DoesNotThrow(() => _person.Equals(otherPerson)); } + + [Test] + public void RealmObject_WhenManaged_ShouldNotThrow() + { + // This is a test to ensure that our weaver is generating valid IL regardless of property configurations + + using (var realm = Realm.GetInstance()) + { + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new NoListProperties()); + }), $"{nameof(NoListProperties)} manage failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new OnlyListProperties()); + }), $"{nameof(OnlyListProperties)} manage failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new MixedProperties1()); + }), $"{nameof(MixedProperties1)} manage failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new MixedProperties2()); + }), $"{nameof(MixedProperties2)} manage failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new OneNonListProperty()); + }), $"{nameof(OneNonListProperty)} manage failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new OneListProperty()); + }), $"{nameof(OneListProperty)} manage failed."); + + Assert.DoesNotThrow(() => realm.Write(() => + { + realm.Manage(new AllPropsClass()); + }), $"{nameof(AllPropsClass)} manage failed."); + } + } + + public class NoListProperties : RealmObject + { + public string Name { get; set; } + + public int Age { get; set; } + } + + public class OnlyListProperties : RealmObject + { + public IList Friends { get; } + + public IList Enemies { get; } + } + + public class MixedProperties1 : RealmObject + { + public string Name { get; set; } + + public IList Friends { get; } + + public int Age { get; set; } + + public IList Enemies { get; } + } + + public class MixedProperties2 : RealmObject + { + public IList Friends { get; } + + public int Age { get; set; } + + public IList Enemies { get; } + + public string Name { get; set; } + } + + public class OneNonListProperty : RealmObject + { + public string Name { get; set; } + } + + public class OneListProperty : RealmObject + { + public IList People { get; } + } + + public class AllPropsClass : RealmObject + { + public string String { get; set; } + public char Char { get; set; } + public byte Byte { get; set; } + public Int16 Int16 { get; set; } + public Int32 Int32 { get; set; } + public Int64 Int64 { get; set; } + public Single Single { get; set; } + public Double Double { get; set; } + public DateTimeOffset DateTimeOffset { get; set; } + public Boolean Boolean { get; set; } + public Byte[] ByteArray { get; set; } + public char? NullableChar { get; set; } + public byte? NullableByte { get; set; } + public Int16? NullableInt16 { get; set; } + public Int32? NullableInt32 { get; set; } + public Int64? NullableInt64 { get; set; } + public Single? NullableSingle { get; set; } + public Double? NullableDouble { get; set; } + public DateTimeOffset? NullableDateTimeOffset { get; set; } + public Boolean? NullableBoolean { get; set; } + } } } diff --git a/Tests/WeaverTests/AssemblyToProcess/AssemblyToProcess.csproj b/Tests/WeaverTests/AssemblyToProcess/AssemblyToProcess.csproj index 4d5e0598d1..cde861561a 100644 --- a/Tests/WeaverTests/AssemblyToProcess/AssemblyToProcess.csproj +++ b/Tests/WeaverTests/AssemblyToProcess/AssemblyToProcess.csproj @@ -49,6 +49,7 @@ + diff --git a/Tests/WeaverTests/AssemblyToProcess/CopyToRealmTestObjects.cs b/Tests/WeaverTests/AssemblyToProcess/CopyToRealmTestObjects.cs new file mode 100644 index 0000000000..657801aa9f --- /dev/null +++ b/Tests/WeaverTests/AssemblyToProcess/CopyToRealmTestObjects.cs @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using Realms; +using System; + +namespace AssemblyToProcess +{ + public class NonNullableProperties : RealmObject + { + public string String { get; set; } + public char Char { get; set; } + public byte Byte { get; set; } + public Int16 Int16 { get; set; } + public Int32 Int32 { get; set; } + public Int64 Int64 { get; set; } + public Single Single { get; set; } + public Double Double { get; set; } + public DateTimeOffset DateTimeOffset { get; set; } + public Boolean Boolean { get; set; } + public byte[] ByteArray { get; set; } + } + + public class NullableProperties : RealmObject + { + public char? Char { get; set; } + public byte? Byte { get; set; } + public Int16? Int16 { get; set; } + public Int32? Int32 { get; set; } + public Int64? Int64 { get; set; } + public Single? Single { get; set; } + public Double? Double { get; set; } + public DateTimeOffset? DateTimeOffset { get; set; } + public Boolean? Boolean { get; set; } + } +} \ No newline at end of file diff --git a/Tests/WeaverTests/PCLAssemblyToProcess/PCLAssemblyToProcess.csproj b/Tests/WeaverTests/PCLAssemblyToProcess/PCLAssemblyToProcess.csproj index 42a73220c2..7989820434 100644 --- a/Tests/WeaverTests/PCLAssemblyToProcess/PCLAssemblyToProcess.csproj +++ b/Tests/WeaverTests/PCLAssemblyToProcess/PCLAssemblyToProcess.csproj @@ -61,6 +61,9 @@ + + CopyToRealmTestObjects.cs +