diff --git a/Parse/Compat/Tuple.cs b/Parse/Compat/Tuple.cs index cc48be68..2c553c6d 100644 --- a/Parse/Compat/Tuple.cs +++ b/Parse/Compat/Tuple.cs @@ -4,7 +4,11 @@ using System.Text; namespace System { - internal static class Tuple { + internal static class Tuple { + // This is useful because it allows for type inference, which normally cannot be done with constructors, but can be done for static methods. + public static Tuple Create(T1 t1, T2 t2) { + return new Tuple(t1, t2); + } } internal class Tuple { diff --git a/Parse/Compat/Type.cs b/Parse/Compat/Type.cs new file mode 100644 index 00000000..3f2fa3ec --- /dev/null +++ b/Parse/Compat/Type.cs @@ -0,0 +1,18 @@ +using System; + +namespace System { + /// + /// Unity does not have an API for GetTypeInfo(), instead they expose most of the methods + /// on System.Reflection.TypeInfo on the type itself. This poses a problem for compatibility + /// with the rest of the C# world, as we expect the result of GetTypeInfo() to be an actual TypeInfo, + /// as well as be able to be converted back to a type using AsType(). + /// + /// This class simply implements some of the simple missing methods on Type to make it as API-compatible + /// as possible to TypeInfo. + /// + internal static class TypeExtensions { + public static Type AsType(this Type type) { + return type; + } + } +} diff --git a/Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs b/Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs new file mode 100644 index 00000000..441ec4dd --- /dev/null +++ b/Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Parse.Internal { + internal interface IObjectSubclassingController { + String GetClassName(Type type); + Type GetType(String className); + + bool IsTypeValid(String className, Type type); + + void RegisterSubclass(Type t); + void UnregisterSubclass(Type t); + + ParseObject Instantiate(String className); + IDictionary GetPropertyMappings(String className); + } +} diff --git a/Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs b/Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs new file mode 100644 index 00000000..3af133d1 --- /dev/null +++ b/Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; + +#if UNITY +using TypeInfo = System.Type; +#endif + +namespace Parse.Internal { + internal class ObjectSubclassInfo { + public ObjectSubclassInfo(Type type, ConstructorInfo constructor) { + TypeInfo = type.GetTypeInfo(); + ClassName = GetClassName(TypeInfo); + Constructor = constructor; + PropertyMappings = type.GetProperties() + .Select(prop => Tuple.Create(prop, prop.GetCustomAttribute(true))) + .Where(t => t.Item2 != null) + .Select(t => Tuple.Create(t.Item1, t.Item2.FieldName)) + .ToDictionary(t => t.Item1.Name, t => t.Item2); + } + + public TypeInfo TypeInfo { get; private set; } + public String ClassName { get; private set; } + public IDictionary PropertyMappings { get; private set; } + private ConstructorInfo Constructor { get; set; } + + public ParseObject Instantiate() { + return (ParseObject)Constructor.Invoke(null); + } + + internal static String GetClassName(TypeInfo type) { + var attribute = type.GetCustomAttribute(); + return attribute != null ? attribute.ClassName : null; + } + } +} diff --git a/Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs b/Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs new file mode 100644 index 00000000..49a598bd --- /dev/null +++ b/Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; + +#if UNITY +using TypeInfo = System.Type; +#endif + +namespace Parse.Internal { + internal class ObjectSubclassingController : IObjectSubclassingController { + private readonly ReaderWriterLockSlim mutex; + private readonly IDictionary registeredSubclasses; + private IDictionary registerActions; + + public ObjectSubclassingController(IDictionary actions) { + mutex = new ReaderWriterLockSlim(); + registeredSubclasses = new Dictionary(); + registerActions = actions.ToDictionary(p => GetClassName(p.Key), p => p.Value); + } + + public String GetClassName(Type type) { + return ObjectSubclassInfo.GetClassName(type.GetTypeInfo()); + } + + public Type GetType(String className) { + ObjectSubclassInfo info = null; + mutex.EnterReadLock(); + registeredSubclasses.TryGetValue(className, out info); + mutex.ExitReadLock(); + + return info != null + ? info.TypeInfo.AsType() + : null; + } + + public bool IsTypeValid(String className, Type type) { + ObjectSubclassInfo subclassInfo = null; + + mutex.EnterReadLock(); + registeredSubclasses.TryGetValue(className, out subclassInfo); + mutex.ExitReadLock(); + + return subclassInfo == null + ? type == typeof(ParseObject) + : subclassInfo.TypeInfo == type.GetTypeInfo(); + } + + public void RegisterSubclass(Type type) { + TypeInfo typeInfo = type.GetTypeInfo(); + if (!typeInfo.IsSubclassOf(typeof(ParseObject))) { + throw new ArgumentException("Cannot register a type that is not a subclass of ParseObject"); + } + + String className = ObjectSubclassInfo.GetClassName(typeInfo); + + try { + // Perform this as a single independent transaction, so we can never get into an + // intermediate state where we *theoretically* register the wrong class due to a + // TOCTTOU bug. + mutex.EnterWriteLock(); + + ObjectSubclassInfo previousInfo = null; + if (registeredSubclasses.TryGetValue(className, out previousInfo)) { + if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) { + // Previous subclass is more specific or equal to the current type, do nothing. + return; + } else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) { + // Previous subclass is parent of new child, fallthrough and actually register + // this class. + /* Do nothing */ + } else { + throw new ArgumentException( + "Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName + + " as the ParseObject subclass of " + className + ". Cannot determine the right class " + + "to use because neither inherits from the other." + ); + } + } + + ConstructorInfo constructor = type.FindConstructor(); + if (constructor == null) { + throw new ArgumentException("Cannot register a type that does not implement the default constructor!"); + } + + registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor); + } finally { + mutex.ExitWriteLock(); + } + + Action toPerform = null; + if (registerActions.TryGetValue(className, out toPerform)) { + toPerform(); + } + } + + public void UnregisterSubclass(Type type) { + mutex.EnterWriteLock(); + registeredSubclasses.Remove(GetClassName(type)); + mutex.ExitWriteLock(); + } + + public ParseObject Instantiate(String className) { + ObjectSubclassInfo info = null; + + mutex.EnterReadLock(); + registeredSubclasses.TryGetValue(className, out info); + mutex.ExitReadLock(); + + return info != null + ? info.Instantiate() + : new ParseObject(className); + } + + public IDictionary GetPropertyMappings(String className) { + ObjectSubclassInfo info = null; + mutex.EnterReadLock(); + registeredSubclasses.TryGetValue(className, out info); + mutex.ExitReadLock(); + + return info != null + ? info.PropertyMappings + : null; + } + } +} diff --git a/Parse/Internal/ParseCorePlugins.cs b/Parse/Internal/ParseCorePlugins.cs index 2951189d..90e3abfb 100644 --- a/Parse/Internal/ParseCorePlugins.cs +++ b/Parse/Internal/ParseCorePlugins.cs @@ -28,7 +28,8 @@ public static ParseCorePlugins Instance { private IParseSessionController sessionController; private IParseUserController userController; private IParsePushController pushController; - private IParsePushChannelsController pushChannelsController; + private IParsePushChannelsController pushChannelsController; + private IObjectSubclassingController subclassingController; #endregion @@ -242,6 +243,25 @@ internal set { currentUserController = value; } } + } + + public IObjectSubclassingController SubclassingController { + get { + lock (mutex) { + subclassingController = subclassingController ?? new ObjectSubclassingController(new Dictionary { + // Do these as explicit closures instead of method references, + // as we should still lazy-load the controllers. + { typeof(ParseUser), () => CurrentUserController.ClearFromMemory() }, + { typeof(ParseInstallation), () => CurrentInstallationController.ClearFromMemory() } + }); + return subclassingController; + } + } + internal set { + lock (mutex) { + subclassingController = value; + } + } } } } diff --git a/Parse/Internal/ReflectionHelpers.cs b/Parse/Internal/ReflectionHelpers.cs index ab092ac7..781202d4 100644 --- a/Parse/Internal/ReflectionHelpers.cs +++ b/Parse/Internal/ReflectionHelpers.cs @@ -50,8 +50,9 @@ internal static bool IsConstructedGenericType(this Type type) { internal static IEnumerable GetConstructors(this Type type) { #if UNITY return type.GetConstructors(); -#else - return type.GetTypeInfo().DeclaredConstructors; +#else + return type.GetTypeInfo().DeclaredConstructors + .Where(c => (c.Attributes & MethodAttributes.Static) == 0); #endif } @@ -78,7 +79,7 @@ from constructor in self.GetConstructors() let types = from p in parameters select p.ParameterType where types.SequenceEqual(parameterTypes) select constructor; - return constructors.Single(); + return constructors.SingleOrDefault(); } internal static PropertyInfo GetProperty(this Type type, string name) { diff --git a/Parse/Parse.Android.csproj b/Parse/Parse.Android.csproj index 9c8317aa..17c34a58 100644 --- a/Parse/Parse.Android.csproj +++ b/Parse/Parse.Android.csproj @@ -73,6 +73,9 @@ + + + diff --git a/Parse/Parse.Unity.csproj b/Parse/Parse.Unity.csproj index 28f6a53e..6c4c9518 100644 --- a/Parse/Parse.Unity.csproj +++ b/Parse/Parse.Unity.csproj @@ -56,6 +56,7 @@ + @@ -90,6 +91,9 @@ + + + diff --git a/Parse/Parse.csproj b/Parse/Parse.csproj index d0ded400..af4d82ef 100644 --- a/Parse/Parse.csproj +++ b/Parse/Parse.csproj @@ -66,6 +66,9 @@ + + + diff --git a/Parse/Parse.iOS.csproj b/Parse/Parse.iOS.csproj index 2ac5a455..622b190a 100644 --- a/Parse/Parse.iOS.csproj +++ b/Parse/Parse.iOS.csproj @@ -136,6 +136,9 @@ + + + diff --git a/Parse/ParseObject.cs b/Parse/ParseObject.cs index 4d7e2fa7..1f0a590c 100644 --- a/Parse/ParseObject.cs +++ b/Parse/ParseObject.cs @@ -28,13 +28,6 @@ namespace Parse { /// public class ParseObject : IEnumerable>, INotifyPropertyChanged { private static readonly string AutoClassName = "_Automatic"; - private static readonly IDictionary, string> - propertyFieldNames = new Dictionary, string>(); - private static readonly IDictionary, Type>> - objectFactories = new Dictionary, Type>>(); - private static readonly IDictionary> - propertyMappings = new Dictionary>(); - private static readonly ReaderWriterLockSlim propertyMappingsLock = new ReaderWriterLockSlim(); internal readonly object mutex = new object(); @@ -68,6 +61,12 @@ internal static IParseObjectController ObjectController { get { return ParseCorePlugins.Instance.ObjectController; } + } + + internal static IObjectSubclassingController SubclassingController { + get { + return ParseCorePlugins.Instance.SubclassingController; + } } #region ParseObject Creation @@ -101,10 +100,10 @@ public ParseObject(string className) { throw new ArgumentException("You must specify a Parse class name when creating a new ParseObject."); } if (AutoClassName.Equals(className)) { - className = GetClassName(this.GetType()); + className = SubclassingController.GetClassName(GetType()); } // If this is supposed to be created by a factory but wasn't, throw an exception - if (this.GetType().Equals(typeof(ParseObject)) && objectFactories.ContainsKey(className)) { + if (!SubclassingController.IsTypeValid(className, GetType())) { throw new ArgumentException( "You must create this type of ParseObject using ParseObject.Create() or the proper subclass."); } @@ -130,8 +129,8 @@ public ParseObject(string className) { /// /// The class of object to create. /// A new ParseObject for the given class name. - public static ParseObject Create(string className) { - return GetFactory(className)(); + public static ParseObject Create(string className) { + return SubclassingController.Instantiate(className); } /// @@ -145,8 +144,8 @@ public static ParseObject Create(string className) { /// A ParseObject without data. public static ParseObject CreateWithoutData(string className, string objectId) { isCreatingPointer.Value = true; - try { - var result = GetFactory(className)(); + try { + var result = SubclassingController.Instantiate(className); result.ObjectId = objectId; result.IsDirty = false; if (result.IsDirty) { @@ -163,8 +162,8 @@ public static ParseObject CreateWithoutData(string className, string objectId) { /// Creates a new ParseObject based upon a given subclass type. /// /// A new ParseObject for the given class name. - public static T Create() where T : ParseObject { - return (T)Create(GetClassName(typeof(T))); + public static T Create() where T : ParseObject { + return (T)SubclassingController.Instantiate(SubclassingController.GetClassName(typeof(T))); } /// @@ -176,7 +175,7 @@ public static T Create() where T : ParseObject { /// The object id for the referenced object. /// A ParseObject without data. public static T CreateWithoutData(string objectId) where T : ParseObject { - return (T)CreateWithoutData(GetClassName(typeof(T)), objectId); + return (T)CreateWithoutData(SubclassingController.GetClassName(typeof(T)), objectId); } // TODO (hallucinogen): add unit test @@ -191,21 +190,9 @@ internal static T FromState(IObjectState state, string defaultClassName) wher #endregion - private static string GetFieldForPropertyName(Type type, string propertyName) { - var key = new Tuple(type, propertyName); - string fieldName; - if (propertyFieldNames.TryGetValue(key, out fieldName)) { - return fieldName; - } - var prop = type.GetProperty(propertyName); - if (prop == null) { - throw new ArgumentException(propertyName + " property does not exist on type " + type); - } - var attr = prop.GetCustomAttribute(); - if (attr == null) { - throw new ArgumentException(propertyName + " does not have a ParseFieldName attribute specified."); - } - propertyFieldNames[key] = fieldName = attr.FieldName; + private static string GetFieldForPropertyName(String className, string propertyName) { + String fieldName = null; + SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out fieldName); return fieldName; } @@ -222,7 +209,7 @@ protected void SetProperty(T value, string propertyName #endif ) { - this[GetFieldForPropertyName(GetType(), propertyName)] = value; + this[GetFieldForPropertyName(ClassName, propertyName)] = value; } /// @@ -238,7 +225,7 @@ protected ParseRelation GetRelationProperty( string propertyName #endif ) where T : ParseObject { - return GetRelation(GetFieldForPropertyName(GetType(), propertyName)); + return GetRelation(GetFieldForPropertyName(ClassName, propertyName)); } /// @@ -272,7 +259,7 @@ string propertyName #endif ) { T result; - if (TryGetValue(GetFieldForPropertyName(GetType(), propertyName), out result)) { + if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out result)) { return result; } return defaultValue; @@ -284,65 +271,18 @@ string propertyName internal virtual void SetDefaultValues() { } - internal static string GetClassName(Type t) { - var attr = t.GetTypeInfo().GetCustomAttribute(); - if (attr == null) { - throw new ArgumentException("No ParseClassName attribute specified on the given subclass."); - } - return attr.ClassName; - } - - /// - /// Gets the appropriate factory for the given class name. If there is no factory for the class, - /// a factory that produces a regular ParseObject will be created. - /// - /// The class name for the ParseObjects the factory will create. - /// - private static Func GetFactory(string className) { - Tuple, Type> result; - if (!objectFactories.TryGetValue(className, out result)) { - return () => new ParseObject(className); - } - return result.Item1; - } - /// /// Registers a custom subclass type with the Parse SDK, enabling strong-typing of those ParseObjects whenever /// they appear. Subclasses must specify the ParseClassName attribute, have a default constructor, and properties /// backed by ParseObject fields should have ParseFieldName attributes supplied. /// /// The ParseObject subclass type to register. - public static void RegisterSubclass() where T : ParseObject, new() { - var className = GetClassName(typeof(T)); - if (className == null) { - throw new ArgumentException("No ParseClassName attribute defined for " + typeof(T)); - } - - Tuple, Type> oldValue; - if (objectFactories.TryGetValue(className, out oldValue)) { - if (typeof(T).GetTypeInfo().IsAssignableFrom(oldValue.Item2.GetTypeInfo())) { - // The old class was already more descendant than the new subclass type. No-op. - return; - } - if (className.Equals(GetClassName(typeof(ParseUser)))) { - ParseUser.ClearInMemoryUser(); - } else if (className.Equals("_Installation")) { - ParseInstallation.ClearInMemoryInstallation(); - } - } - objectFactories[className] = new Tuple, Type>(() => new T(), typeof(T)); + public static void RegisterSubclass() where T : ParseObject, new() { + SubclassingController.RegisterSubclass(typeof(T)); } - internal static void UnregisterSubclass(string className) { - objectFactories.Remove(className); - } - - internal static Type GetType(string className) { - Tuple, Type> result; - if (!objectFactories.TryGetValue(className, out result)) { - return typeof(ParseObject); - } - return result.Item2; + internal static void UnregisterSubclass() where T: ParseObject, new() { + SubclassingController.UnregisterSubclass(typeof(T)); } /// @@ -1613,69 +1553,34 @@ public static ParseQuery GetQuery(string className) { // experience anyway, especially with LINQ integration, since you'll get // strongly-typed queries and compile-time checking of property names and // types. - if (GetType(className) != typeof(ParseObject)) { + if (SubclassingController.GetType(className) != null) { throw new ArgumentException( "Use the class-specific query properties for class " + className, "className"); } return new ParseQuery(className); } - /// - /// Gets the set of fieldName->propertyName mappings for the current class. - /// - internal IDictionary PropertyMappings { - get { - IDictionary mappings; - // Try to get it with only a read lock - propertyMappingsLock.EnterReadLock(); - try { - if (propertyMappings.TryGetValue(this.GetType(), out mappings)) { - return mappings; - } - } finally { - propertyMappingsLock.ExitReadLock(); - } - - propertyMappingsLock.EnterUpgradeableReadLock(); - try { - // Now that we have an upgradeable read lock, determine whether we need to - // upgrade. If another thread already beat us to setting this value, just - // move on without acquiring the write lock. - if (!propertyMappings.TryGetValue(this.GetType(), out mappings)) { - mappings = new Dictionary(); - foreach (var prop in this.GetType().GetProperties()) { - var attr = prop.GetCustomAttribute(true); - if (attr != null) { - mappings[attr.FieldName] = prop.Name; - } - } - propertyMappingsLock.EnterWriteLock(); - try { - propertyMappings[this.GetType()] = mappings; - } finally { - propertyMappingsLock.ExitWriteLock(); - } - } - return mappings; - } finally { - propertyMappingsLock.ExitUpgradeableReadLock(); - } - } - } - /// /// Raises change notifications for all properties associated with the given /// field names. If fieldNames is null, this will notify for all known field-linked /// properties (e.g. this happens when we recalculate all estimated data from scratch) /// - protected void OnFieldsChanged(IEnumerable fieldNames) { - var mappings = PropertyMappings; - fieldNames = fieldNames ?? mappings.Keys; - foreach (var fieldName in fieldNames) { - string propName; - if (mappings.TryGetValue(fieldName, out propName)) { - OnPropertyChanged(propName); - } + protected void OnFieldsChanged(IEnumerable fieldNames) { + var mappings = SubclassingController.GetPropertyMappings(ClassName); + IEnumerable properties; + + if (fieldNames != null && mappings != null) { + properties = from m in mappings + join f in fieldNames on m.Value equals f + select m.Key; + } else if (mappings != null) { + properties = mappings.Keys; + } else { + properties = Enumerable.Empty(); + } + + foreach (var property in properties) { + OnPropertyChanged(property); } OnPropertyChanged("Item[]"); } diff --git a/Parse/ParseQuery.cs b/Parse/ParseQuery.cs index 801ccdeb..7cbea00e 100644 --- a/Parse/ParseQuery.cs +++ b/Parse/ParseQuery.cs @@ -60,6 +60,12 @@ internal static IParseQueryController QueryController { get { return ParseCorePlugins.Instance.QueryController; } + } + + internal static IObjectSubclassingController SubclassingController { + get { + return ParseCorePlugins.Instance.SubclassingController; + } } /// @@ -189,7 +195,7 @@ private IDictionary MergeWhereClauses(IDictionary public ParseQuery() - : this(ParseObject.GetClassName(typeof(T))) { + : this(SubclassingController.GetClassName(typeof(T))) { } /// diff --git a/Parse/ParseRelation.cs b/Parse/ParseRelation.cs index cf81e6ee..be518563 100644 --- a/Parse/ParseRelation.cs +++ b/Parse/ParseRelation.cs @@ -27,6 +27,12 @@ internal ParseRelationBase(ParseObject parent, string key) { internal ParseRelationBase(ParseObject parent, string key, string targetClassName) : this(parent, key) { this.targetClassName = targetClassName; + } + + internal static IObjectSubclassingController SubclassingController { + get { + return ParseCorePlugins.Instance.SubclassingController; + } } internal void EnsureParentAndKey(ParseObject parent, string key) { @@ -85,28 +91,18 @@ internal static ParseRelationBase CreateRelation(ParseObject parent, #if UNITY if (PlatformHooks.IsCompiledByIL2CPP) { return CreateRelation(parent, key, targetClassName); - } else { - var targetType = ParseObject.GetType(targetClassName); - Expression>> createRelationExpr = - () => CreateRelation(parent, key, targetClassName); - var createRelationMethod = - ((MethodCallExpression)createRelationExpr.Body) - .Method - .GetGenericMethodDefinition() - .MakeGenericMethod(targetType); - return (ParseRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); } -#else - var targetType = ParseObject.GetType(targetClassName); - Expression>> createRelationExpr = - () => CreateRelation(parent, key, targetClassName); - var createRelationMethod = - ((MethodCallExpression)createRelationExpr.Body) - .Method - .GetGenericMethodDefinition() - .MakeGenericMethod(targetType); - return (ParseRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); -#endif +#endif + var targetType = SubclassingController.GetType(targetClassName) ?? typeof(ParseObject); + + Expression>> createRelationExpr = + () => CreateRelation(parent, key, targetClassName); + var createRelationMethod = + ((MethodCallExpression)createRelationExpr.Body) + .Method + .GetGenericMethodDefinition() + .MakeGenericMethod(targetType); + return (ParseRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName }); } private static ParseRelation CreateRelation(ParseObject parent, string key, string targetClassName) diff --git a/ParseTest.Unit/CurrentInstallationControllerTests.cs b/ParseTest.Unit/CurrentInstallationControllerTests.cs index 174d85ca..b9511264 100644 --- a/ParseTest.Unit/CurrentInstallationControllerTests.cs +++ b/ParseTest.Unit/CurrentInstallationControllerTests.cs @@ -17,7 +17,7 @@ public void SetUp() { [TearDown] public void TearDown() { - ParseObject.UnregisterSubclass(ParseObject.GetClassName(typeof(ParseInstallation))); + ParseObject.UnregisterSubclass(); } [Test] diff --git a/ParseTest.Unit/CurrentUserControllerTests.cs b/ParseTest.Unit/CurrentUserControllerTests.cs index 2d0410fe..12b87906 100644 --- a/ParseTest.Unit/CurrentUserControllerTests.cs +++ b/ParseTest.Unit/CurrentUserControllerTests.cs @@ -16,8 +16,8 @@ public void SetUp() { } [TearDown] - public void TearDown() { - ParseObject.UnregisterSubclass(ParseObject.GetClassName(typeof(ParseUser))); + public void TearDown() { + ParseObject.UnregisterSubclass(); } [Test] diff --git a/ParseTest.Unit/InstallationTests.cs b/ParseTest.Unit/InstallationTests.cs index 05d9a41e..0295e53a 100644 --- a/ParseTest.Unit/InstallationTests.cs +++ b/ParseTest.Unit/InstallationTests.cs @@ -20,8 +20,8 @@ public void SetUp() { public void TearDown() { ParseCorePlugins.Instance.ObjectController = null; ParseCorePlugins.Instance.CurrentInstallationController = null; - ParseCorePlugins.Instance.CurrentUserController = null; - ParseObject.UnregisterSubclass("_Installation"); + ParseCorePlugins.Instance.CurrentUserController = null; + ParseObject.UnregisterSubclass(); } [Test] diff --git a/ParseTest.Unit/SessionTests.cs b/ParseTest.Unit/SessionTests.cs index fb22cfeb..1e60741f 100644 --- a/ParseTest.Unit/SessionTests.cs +++ b/ParseTest.Unit/SessionTests.cs @@ -21,8 +21,8 @@ public void SetUp() { public void TearDown() { ParseCorePlugins.Instance.SessionController = null; ParseCorePlugins.Instance.CurrentUserController = null; - ParseObject.UnregisterSubclass("_Session"); - ParseObject.UnregisterSubclass("_User"); + ParseObject.UnregisterSubclass(); + ParseObject.UnregisterSubclass(); } [Test] diff --git a/ParseTest.Unit/UserTests.cs b/ParseTest.Unit/UserTests.cs index 404bef69..cee65152 100644 --- a/ParseTest.Unit/UserTests.cs +++ b/ParseTest.Unit/UserTests.cs @@ -23,8 +23,8 @@ public void TearDown() { ParseCorePlugins.Instance.CurrentUserController = null; ParseCorePlugins.Instance.SessionController = null; ParseCorePlugins.Instance.ObjectController = null; - ParseObject.UnregisterSubclass("_User"); - ParseObject.UnregisterSubclass("_Session"); + ParseObject.UnregisterSubclass(); + ParseObject.UnregisterSubclass(); } [Test]