From c91a082e84b3d1a14c67882def0461509d64c087 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Thu, 8 Mar 2012 11:05:20 +0000 Subject: [PATCH] Pool can now be used with types not derived from IRecyclable and/or without a public default constructor; consolidated type-related helper methods into a common helper class (TypeHelper.cs); optimized GetFieldInfosIncludingBaseClasses() method git-svn-id: file:///srv/devel/repo-conversion/nusu@268 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (net-4.0).csproj | 12 +- Nuclex.Support (xna-4.0-phone7).csproj | 12 +- Nuclex.Support (xna-4.0-xbox360).csproj | 12 +- Source/Cloning/ClonerHelpers.cs | 81 ---------- .../ExpressionTreeCloner.FieldBased.cs | 8 +- Source/Cloning/ReflectionCloner.cs | 8 +- Source/Cloning/SerializationCloner.cs | 8 +- Source/Collections/PairPriorityQueue.cs | 6 +- Source/Collections/ParentingCollection.cs | 5 +- Source/Collections/Pool.cs | 30 +++- Source/Plugins/Employer.cs | 2 +- Source/Plugins/FactoryEmployer.cs | 24 +-- Source/Plugins/InstanceEmployer.cs | 16 +- Source/Plugins/PluginHelper.Test.cs | 83 ---------- Source/Plugins/PluginHelper.cs | 44 ------ Source/Plugins/PrototypeFactory.cs | 20 +-- ...onerHelpers.Test.cs => TypeHelper.Test.cs} | 51 +++++- Source/TypeHelper.cs | 147 ++++++++++++++++++ 18 files changed, 277 insertions(+), 292 deletions(-) delete mode 100644 Source/Cloning/ClonerHelpers.cs delete mode 100644 Source/Plugins/PluginHelper.Test.cs delete mode 100644 Source/Plugins/PluginHelper.cs rename Source/{Cloning/ClonerHelpers.Test.cs => TypeHelper.Test.cs} (52%) create mode 100644 Source/TypeHelper.cs diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj index 6981346..38771f8 100644 --- a/Nuclex.Support (net-4.0).csproj +++ b/Nuclex.Support (net-4.0).csproj @@ -58,10 +58,6 @@ - - - ClonerHelpers.cs - ExpressionTreeCloner.cs @@ -253,10 +249,6 @@ NoPluginAttribute.cs - - - PluginHelper.cs - PluginHost.cs @@ -285,6 +277,10 @@ ObservableHelper.cs + + + \TypeHelper.cs + Semaphore.cs diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj index 2c3c325..27676ea 100644 --- a/Nuclex.Support (xna-4.0-phone7).csproj +++ b/Nuclex.Support (xna-4.0-phone7).csproj @@ -89,10 +89,6 @@ - - - ClonerHelpers.cs - ExpressionTreeCloner.cs @@ -284,10 +280,6 @@ NoPluginAttribute.cs - - - PluginHelper.cs - PluginHost.cs @@ -316,6 +308,10 @@ ObservableHelper.cs + + + \TypeHelper.cs + Semaphore.cs diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj index bd0dbe3..44ac1db 100644 --- a/Nuclex.Support (xna-4.0-xbox360).csproj +++ b/Nuclex.Support (xna-4.0-xbox360).csproj @@ -100,10 +100,6 @@ - - - ClonerHelpers.cs - ExpressionTreeCloner.cs @@ -295,10 +291,6 @@ NoPluginAttribute.cs - - - PluginHelper.cs - PluginHost.cs @@ -327,6 +319,10 @@ ObservableHelper.cs + + + \TypeHelper.cs + Semaphore.cs diff --git a/Source/Cloning/ClonerHelpers.cs b/Source/Cloning/ClonerHelpers.cs deleted file mode 100644 index a35f595..0000000 --- a/Source/Cloning/ClonerHelpers.cs +++ /dev/null @@ -1,81 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2012 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Reflection; - -namespace Nuclex.Support.Cloning { - - /// Contains helper methods for the cloners - internal static class ClonerHelpers { - - /// - /// Returns all the fields of a type, working around a weird reflection issue - /// where explicitly declared fields in base classes are returned, but not - /// automatic property backing fields. - /// - /// Type whose fields will be returned - /// Binding flags to use when querying the fields - /// All of the type's fields, including its base types - public static FieldInfo[] GetFieldInfosIncludingBaseClasses( - Type type, BindingFlags bindingFlags - ) { - FieldInfo[] fieldInfos = type.GetFields(bindingFlags); - - // If this class doesn't have a base, don't waste any time - if(type.BaseType == typeof(object)) { - return fieldInfos; - } else { // Otherwise, collect all types up to the furthest base class - var fieldInfoList = new List(fieldInfos); - while(type.BaseType != typeof(object)) { - type = type.BaseType; - fieldInfos = type.GetFields(bindingFlags); - - // Look for fields we do not have listed yet and merge them into the main list - for(int index = 0; index < fieldInfos.Length; ++index) { - bool found = false; - - for(int searchIndex = 0; searchIndex < fieldInfoList.Count; ++searchIndex) { - bool match = - (fieldInfoList[searchIndex].DeclaringType == fieldInfos[index].DeclaringType) && - (fieldInfoList[searchIndex].Name == fieldInfos[index].Name); - - if(match) { - found = true; - break; - } - } - - if(!found) { - fieldInfoList.Add(fieldInfos[index]); - } - } - } - - return fieldInfoList.ToArray(); - } - } - - } - -} // namespace Nuclex.Support.Cloning diff --git a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs index 1fa5686..4382760 100644 --- a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs +++ b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs @@ -185,8 +185,8 @@ namespace Nuclex.Support.Cloning { ); // Enumerate all of the type's fields and generate transfer expressions for each - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - clonedType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = clonedType.GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; @@ -495,8 +495,8 @@ namespace Nuclex.Support.Cloning { ICollection transferExpressions ) { // Enumerate all of the type's fields and generate transfer expressions for each - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - clonedType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = clonedType.GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; diff --git a/Source/Cloning/ReflectionCloner.cs b/Source/Cloning/ReflectionCloner.cs index f22d29b..057b8fb 100644 --- a/Source/Cloning/ReflectionCloner.cs +++ b/Source/Cloning/ReflectionCloner.cs @@ -167,8 +167,8 @@ namespace Nuclex.Support.Cloning { object clone = FormatterServices.GetUninitializedObject(originalType); #endif - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; @@ -249,8 +249,8 @@ namespace Nuclex.Support.Cloning { object clone = FormatterServices.GetUninitializedObject(originalType); #endif - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; diff --git a/Source/Cloning/SerializationCloner.cs b/Source/Cloning/SerializationCloner.cs index 24698dc..31292cc 100644 --- a/Source/Cloning/SerializationCloner.cs +++ b/Source/Cloning/SerializationCloner.cs @@ -118,8 +118,8 @@ namespace Nuclex.Support.Cloning { ) { Type originalType = objectToSerialize.GetType(); - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; @@ -143,8 +143,8 @@ namespace Nuclex.Support.Cloning { ) { Type originalType = deserializedObject.GetType(); - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; diff --git a/Source/Collections/PairPriorityQueue.cs b/Source/Collections/PairPriorityQueue.cs index a5fbf86..7461d0a 100644 --- a/Source/Collections/PairPriorityQueue.cs +++ b/Source/Collections/PairPriorityQueue.cs @@ -30,9 +30,9 @@ namespace Nuclex.Support.Collections { /// priority data type implements the IComparable interface, the user does not /// even /// - public class PairPriorityQueue - : ICollection, IEnumerable> { - + public class PairPriorityQueue : + ICollection, IEnumerable> { + #region class PairComparer /// Compares two priority queue entries based on their priority diff --git a/Source/Collections/ParentingCollection.cs b/Source/Collections/ParentingCollection.cs index 4042a0c..097fff2 100644 --- a/Source/Collections/ParentingCollection.cs +++ b/Source/Collections/ParentingCollection.cs @@ -98,13 +98,12 @@ namespace Nuclex.Support.Collections { // starting from the last item in the assumption that this is the fastest // way to empty a list without causing excessive shiftings in the array. for(int index = base.Count - 1; index >= 0; --index) { - IDisposable disposable = base[index] as IDisposable; // If the item is disposable, destroy it now - if(disposable != null) + if(disposable != null) { disposable.Dispose(); - + } } base.ClearItems(); diff --git a/Source/Collections/Pool.cs b/Source/Collections/Pool.cs index e6cbc57..75a04fc 100644 --- a/Source/Collections/Pool.cs +++ b/Source/Collections/Pool.cs @@ -45,18 +45,35 @@ namespace Nuclex.Support.Collections { /// automatically call its IRecyclable.Recycle() method. /// /// - public class Pool where TItem : class, new() { + public class Pool { /// Default number of recyclable objects the pool will store public const int DefaultPoolSize = 64; /// Initializes a new pool using the default capacity - public Pool() : this(DefaultPoolSize) { } + /// Delegate that will be used to create new items + /// Delegate that will be used to recycle items + public Pool(Func createNewDelegate = null, Action recycleDelegate = null) : + this(DefaultPoolSize, createNewDelegate, recycleDelegate) { } /// Initializes a new pool using a user-specified capacity /// Capacity of the pool - public Pool(int capacity) { + /// Delegate that will be used to create new items + /// Delegate that will be used to recycle items + public Pool( + int capacity, Func createNewDelegate = null, Action recycleDelegate = null + ) { Capacity = capacity; + + if(createNewDelegate == null) { + createNewDelegate = new Func(Activator.CreateInstance); + } + if(recycleDelegate == null) { + recycleDelegate = new Action(callRecycleIfSupported); + } + + this.createNewDelegate = createNewDelegate; + this.recycleDelegate = recycleDelegate; } /// @@ -68,7 +85,7 @@ namespace Nuclex.Support.Collections { if(this.items.Count > 0) { return this.items.Dequeue(); } else { - return new TItem(); + return this.createNewDelegate(); } } } @@ -82,13 +99,14 @@ namespace Nuclex.Support.Collections { // Call Recycle() when the object is redeemed (instead of when it leaves // the pool again) in order to eliminate any references the object may hold // to other objects. - callRecycleIfSupported(item); + this.recycleDelegate(item); lock(this) { if(this.items.Count < this.capacity) { this.items.Enqueue(item); } } + } /// Number of objects the pool can retain @@ -125,6 +143,8 @@ namespace Nuclex.Support.Collections { /// Required because the Queue class doesn't allow this value to be retrieved /// private int capacity; + private Func createNewDelegate; + private Action recycleDelegate; } diff --git a/Source/Plugins/Employer.cs b/Source/Plugins/Employer.cs index ebf538d..57f450f 100644 --- a/Source/Plugins/Employer.cs +++ b/Source/Plugins/Employer.cs @@ -36,7 +36,7 @@ namespace Nuclex.Support.Plugins { /// Type which will be assessed /// True if the type can be employed, otherwise false public virtual bool CanEmploy(Type type) { - return PluginHelper.HasDefaultConstructor(type); + return type.HasDefaultConstructor(); } /// Employs the specified plugin type diff --git a/Source/Plugins/FactoryEmployer.cs b/Source/Plugins/FactoryEmployer.cs index 7922f04..f5b11c0 100644 --- a/Source/Plugins/FactoryEmployer.cs +++ b/Source/Plugins/FactoryEmployer.cs @@ -24,7 +24,7 @@ using System.Collections.Generic; namespace Nuclex.Support.Plugins { /// Employer to create factories of suiting types found in plugins - /// + /// /// Interface or base class that the types need to implement /// /// @@ -41,12 +41,12 @@ namespace Nuclex.Support.Plugins { /// a human-readable name, capabilities or an icon. /// /// - public class FactoryEmployer : Employer where ProductType : class { + public class FactoryEmployer : Employer where TProduct : class { #region class ConcreteFactory /// Concrete factory for the types in a plugin assembly - private class ConcreteFactory : IAbstractFactory, IAbstractFactory { + private class ConcreteFactory : IAbstractFactory, IAbstractFactory { /// /// Initializes a factory and configures it for the specified product @@ -58,8 +58,8 @@ namespace Nuclex.Support.Plugins { /// Create a new instance of the type the factory is configured to /// The newly created instance - public ProductType CreateInstance() { - return (ProductType)Activator.CreateInstance(this.concreteType); + public TProduct CreateInstance() { + return (TProduct)Activator.CreateInstance(this.concreteType); } /// Create a new instance of the type the factory is configured to @@ -77,11 +77,11 @@ namespace Nuclex.Support.Plugins { /// Initializes a new FactoryEmployer public FactoryEmployer() { - this.employedFactories = new List>(); + this.employedFactories = new List>(); } /// List of all factories that the instance employer has created - public List> Factories { + public List> Factories { get { return this.employedFactories; } } @@ -90,20 +90,20 @@ namespace Nuclex.Support.Plugins { /// True if the type can be employed public override bool CanEmploy(Type type) { return - PluginHelper.HasDefaultConstructor(type) && - typeof(ProductType).IsAssignableFrom(type) && + type.HasDefaultConstructor() && + typeof(TProduct).IsAssignableFrom(type) && !type.ContainsGenericParameters; } /// Employs the specified plugin type /// Type to be employed public override void Employ(Type type) { - if(!PluginHelper.HasDefaultConstructor(type)) { + if(!type.HasDefaultConstructor()) { throw new MissingMethodException( "Cannot employ type because it does not have a public default constructor" ); } - if(!typeof(ProductType).IsAssignableFrom(type)) { + if(!typeof(TProduct).IsAssignableFrom(type)) { throw new InvalidCastException( "Cannot employ type because it cannot be cast to the factory's product type" ); @@ -118,7 +118,7 @@ namespace Nuclex.Support.Plugins { } /// All factories that the instance employer has created - private List> employedFactories; + private List> employedFactories; } diff --git a/Source/Plugins/InstanceEmployer.cs b/Source/Plugins/InstanceEmployer.cs index 57d32ff..24a8a19 100644 --- a/Source/Plugins/InstanceEmployer.cs +++ b/Source/Plugins/InstanceEmployer.cs @@ -24,7 +24,7 @@ using System.Collections.Generic; namespace Nuclex.Support.Plugins { /// Employer that directly creates instances of the types in a plugin - /// Interface or base class required for the employed types + /// Interface or base class required for the employed types /// /// /// This employer directly creates an instance of any type in a plugin assembly that @@ -44,15 +44,15 @@ namespace Nuclex.Support.Plugins { /// factory would then be implemented on the plugin side. /// /// - public class InstanceEmployer : Employer { + public class InstanceEmployer : Employer { /// Initializes a new instance employer public InstanceEmployer() { - this.employedInstances = new List(); + this.employedInstances = new List(); } /// All instances that have been employed - public List Instances { + public List Instances { get { return this.employedInstances; } } @@ -61,19 +61,19 @@ namespace Nuclex.Support.Plugins { /// True if the type can be employed public override bool CanEmploy(Type type) { return - PluginHelper.HasDefaultConstructor(type) && - typeof(T).IsAssignableFrom(type) && + type.HasDefaultConstructor() && + typeof(TEmployedType).IsAssignableFrom(type) && !type.ContainsGenericParameters; } /// Employs the specified plugin type /// Type to be employed public override void Employ(Type type) { - this.employedInstances.Add((T)Activator.CreateInstance(type)); + this.employedInstances.Add((TEmployedType)Activator.CreateInstance(type)); } /// All instances employed by the instance employer - private List employedInstances; + private List employedInstances; } diff --git a/Source/Plugins/PluginHelper.Test.cs b/Source/Plugins/PluginHelper.Test.cs deleted file mode 100644 index 110f191..0000000 --- a/Source/Plugins/PluginHelper.Test.cs +++ /dev/null @@ -1,83 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2012 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.IO; - -#if UNITTEST - -using NUnit.Framework; - -namespace Nuclex.Support.Plugins { - - /// Unit Test for the plugin helper class - [TestFixture] - internal class PluginHelperTest { - - #region class NoDefaultConstructor - - /// Test class that doesn't have a default constructor - private class NoDefaultConstructor { - /// Initializes a new instance of the test class - /// Dummy argument so this is no default constructor - public NoDefaultConstructor(int dummy) { } - } - - #endregion // class NoDefaultConstructor - - #region class NonPublicDefaultConstructor - - /// Test class that has a non-public default constructor - private class NonPublicDefaultConstructor { - /// Initializes a new instance of the test class - protected NonPublicDefaultConstructor() { } - } - - #endregion // class NonPublicDefaultConstructor - - #region class PublicDefaultConstructor - - /// Test class that has a public default constructor - private class PublicDefaultConstructor { - /// Initializes a new instance of the test class - public PublicDefaultConstructor() { } - } - - #endregion // class PublicDefaultConstructor - - /// Tests whether the default constructor detection works as expected - [Test] - public void TestDefaultConstructorDetection() { - Assert.IsFalse( - PluginHelper.HasDefaultConstructor(typeof(NoDefaultConstructor)) - ); - Assert.IsFalse( - PluginHelper.HasDefaultConstructor(typeof(NonPublicDefaultConstructor)) - ); - Assert.IsTrue( - PluginHelper.HasDefaultConstructor(typeof(PublicDefaultConstructor)) - ); - } - - } - -} // namespace Nuclex.Support.Plugins - -#endif // UNITTEST diff --git a/Source/Plugins/PluginHelper.cs b/Source/Plugins/PluginHelper.cs deleted file mode 100644 index 4c7e046..0000000 --- a/Source/Plugins/PluginHelper.cs +++ /dev/null @@ -1,44 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2012 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Reflection; - -namespace Nuclex.Support.Plugins { - - /// Supporting functions for the plugin classes - public static class PluginHelper { - - /// Determines whether the given type has a default constructor - /// Type which is to be checked - /// True if the type has a default constructor - public static bool HasDefaultConstructor(Type type) { - ConstructorInfo[] constructors = type.GetConstructors(); - - foreach(ConstructorInfo constructor in constructors) - if(constructor.IsPublic && (constructor.GetParameters().Length == 0)) - return true; - - return false; - } - - } - -} // namespace Nuclex.Support.Plugins diff --git a/Source/Plugins/PrototypeFactory.cs b/Source/Plugins/PrototypeFactory.cs index ce465c6..49b22ee 100644 --- a/Source/Plugins/PrototypeFactory.cs +++ b/Source/Plugins/PrototypeFactory.cs @@ -7,16 +7,16 @@ namespace Nuclex.Support.Plugins { #if !NO_CLONING /// Factory that creates instances by cloning a prototype - /// Type of product created by the factory - /// Type of the prototype that will be cloned - public class PrototypeFactory : - IAbstractFactory, IAbstractFactory, IDisposable - where ProductType : class - where ConcreteType : class, ICloneable { + /// Type of product created by the factory + /// Type of the prototype that will be cloned + public class PrototypeFactory : + IAbstractFactory, IAbstractFactory, IDisposable + where TProduct : class + where TConcreteType : class, ICloneable { /// Initializes a new prototype based factory /// Prototype instance that will be cloned - public PrototypeFactory(ConcreteType prototype) { + public PrototypeFactory(TConcreteType prototype) { this.prototype = prototype; } @@ -24,8 +24,8 @@ namespace Nuclex.Support.Plugins { /// Creates a new instance of the type to which the factory is specialized /// /// The newly created instance - public ProductType CreateInstance() { - return (ProductType)this.prototype.Clone(); + public TProduct CreateInstance() { + return (TProduct)this.prototype.Clone(); } /// @@ -49,7 +49,7 @@ namespace Nuclex.Support.Plugins { } /// The prototype object - private ConcreteType prototype; + private TConcreteType prototype; } diff --git a/Source/Cloning/ClonerHelpers.Test.cs b/Source/TypeHelper.Test.cs similarity index 52% rename from Source/Cloning/ClonerHelpers.Test.cs rename to Source/TypeHelper.Test.cs index 20d5aa7..050a9a8 100644 --- a/Source/Cloning/ClonerHelpers.Test.cs +++ b/Source/TypeHelper.Test.cs @@ -25,11 +25,42 @@ using System.Reflection; using NUnit.Framework; -namespace Nuclex.Support.Cloning { +namespace Nuclex.Support { - /// Unit Test for the cloner helpers + /// Unit Test for the strign segment class [TestFixture] - internal class ClonerHelpersTest { + internal class TypeHelperTest { + + #region class NoDefaultConstructor + + /// Test class that doesn't have a default constructor + private class NoDefaultConstructor { + /// Initializes a new instance of the test class + /// Dummy argument so this is no default constructor + public NoDefaultConstructor(int dummy) { } + } + + #endregion // class NoDefaultConstructor + + #region class NonPublicDefaultConstructor + + /// Test class that has a non-public default constructor + private class NonPublicDefaultConstructor { + /// Initializes a new instance of the test class + protected NonPublicDefaultConstructor() { } + } + + #endregion // class NonPublicDefaultConstructor + + #region class PublicDefaultConstructor + + /// Test class that has a public default constructor + private class PublicDefaultConstructor { + /// Initializes a new instance of the test class + public PublicDefaultConstructor() { } + } + + #endregion // class PublicDefaultConstructor #region class Base @@ -61,8 +92,8 @@ namespace Nuclex.Support.Cloning { /// [Test] public void CanGetBackingFieldsForPropertiesInBaseClasses() { - FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( - typeof(Derived), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + FieldInfo[] fieldInfos = typeof(Derived).GetFieldInfosIncludingBaseClasses( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); Assert.AreEqual(4, fieldInfos.Length); } @@ -79,8 +110,16 @@ namespace Nuclex.Support.Cloning { }; } + /// Tests whether the default constructor detection works as expected + [Test] + public void TestDefaultConstructorDetection() { + Assert.IsFalse(typeof(NoDefaultConstructor).HasDefaultConstructor()); + Assert.IsFalse(typeof(NonPublicDefaultConstructor).HasDefaultConstructor()); + Assert.IsTrue(typeof(PublicDefaultConstructor).HasDefaultConstructor()); + } + } -} // namespace Nuclex.Support.Cloning +} // namespace Nuclex.Support #endif // UNITTEST diff --git a/Source/TypeHelper.cs b/Source/TypeHelper.cs new file mode 100644 index 0000000..4601898 --- /dev/null +++ b/Source/TypeHelper.cs @@ -0,0 +1,147 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2012 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Nuclex.Support { + + /// Helper methods for the reflection Type class + public static class TypeHelper { + + #region class MemberInfoComparer + + /// Determines whether member informations relate to the same member + private class MemberInfoComparer : IEqualityComparer { + + /// Default instance of the comparer + public static readonly MemberInfoComparer Default = new MemberInfoComparer(); + + /// Checks whether two member informations are equal + /// Informations about the left member in the comaprison + /// Informations about the right member in the comparison + /// True if the two member informations relate to the same member + public bool Equals(MemberInfo left, MemberInfo right) { + return + (left.DeclaringType == right.DeclaringType) && + (left.Name == right.Name); + } + + /// Determines the hash code of the specified member informations + /// + /// Member informations whose hash code will be determined + /// + /// The hash code of the specified member informations + public int GetHashCode(MemberInfo memberInfo) { + return (memberInfo.DeclaringType.GetHashCode() ^ memberInfo.Name.GetHashCode()); + } + + } + + #endregion // class MemberInfoComparer + + /// Determines whether the given type has a default constructor + /// Type which is to be checked + /// True if the type has a default constructor + public static bool HasDefaultConstructor(this Type type) { + ConstructorInfo[] constructors = type.GetConstructors(); + + for(int index = 0; index < constructors.Length; ++index) { + ConstructorInfo constructor = constructors[index]; + if(constructor.IsPublic && (constructor.GetParameters().Length == 0)) { + return true; + } + } + + return false; + } + +#if XBOX || WINDOWS_PHONE + /// + /// Returns all the fields of a type, including those defined in the type's base classes + /// + /// Type whose fields will be returned + /// Binding flags to use when querying the fields + /// All of the type's fields, including its base types + public static FieldInfo[] GetFieldInfosIncludingBaseClasses( + this Type type, BindingFlags bindingFlags + ) { + FieldInfo[] fieldInfos = type.GetFields(bindingFlags); + + // If this class doesn't have a base, don't waste any time + if(type.BaseType != typeof(object)) { + var fieldInfoSet = new Dictionary(MemberInfoComparer.Default); + for(int index = 0; index < fieldInfos.Length; ++index) { + fieldInfoSet.Add(fieldInfos[index], null); + } + + while(type.BaseType != typeof(object)) { + type = type.BaseType; + fieldInfos = type.GetFields(bindingFlags); + + for(int index = 0; index < fieldInfos.Length; ++index) { + FieldInfo fieldInfo = fieldInfos[index]; + if(!fieldInfoSet.ContainsKey(fieldInfo)) { + fieldInfoSet.Add(fieldInfo, null); + } + } + } + + fieldInfos = new FieldInfo[fieldInfoSet.Count]; + fieldInfoSet.Keys.CopyTo(fieldInfos, 0); + } + + return fieldInfos; + } +#else + /// + /// Returns all the fields of a type, including those defined in the type's base classes + /// + /// Type whose fields will be returned + /// Binding flags to use when querying the fields + /// All of the type's fields, including its base types + public static FieldInfo[] GetFieldInfosIncludingBaseClasses( + this Type type, BindingFlags bindingFlags + ) { + FieldInfo[] fieldInfos = type.GetFields(bindingFlags); + + // If this class doesn't have a base, don't waste any time + if(type.BaseType != typeof(object)) { + var fieldInfoSet = new HashSet(fieldInfos, MemberInfoComparer.Default); + while(type.BaseType != typeof(object)) { + type = type.BaseType; + fieldInfos = type.GetFields(bindingFlags); + for(int index = 0; index < fieldInfos.Length; ++index) { + fieldInfoSet.Add(fieldInfos[index]); + } + } + + fieldInfos = new FieldInfo[fieldInfoSet.Count]; + fieldInfoSet.CopyTo(fieldInfos); + } + + return fieldInfos; + } +#endif + + } + +} // namespace Nuclex.Support