SPRNET-1318 introduce local cache of merged ObjectDefinitions to reduce impact of AppContext.Dispose() calls
This commit is contained in:
@@ -257,7 +257,7 @@ namespace Spring.Objects.Factory.Support
|
||||
ApplyPropertyValues(name, definition, new ObjectWrapper(instance), definition.PropertyValues);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Apply the property values of the object definition with the supplied
|
||||
/// <paramref name="name"/> to the supplied <paramref name="instance"/>.
|
||||
@@ -480,8 +480,8 @@ namespace Spring.Objects.Factory.Support
|
||||
//have to clone the collection before iterating it to avoid arbitrary code in the objects' Dispose()
|
||||
// that might permit re-entering the DisposableInnerObjects collections during the iteration to destroy them
|
||||
// see https://jira.springframework.org/browse/SPRNET-1334
|
||||
|
||||
ISet clone = (ISet) DisposableInnerObjects.Clone();
|
||||
|
||||
ISet clone = (ISet)DisposableInnerObjects.Clone();
|
||||
|
||||
foreach (object o in clone)
|
||||
{
|
||||
@@ -792,44 +792,44 @@ namespace Spring.Objects.Factory.Support
|
||||
ignoredDependencyInterfaces.Add(type);
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Create an object instance for the given object definition.
|
||||
// /// </summary>
|
||||
// /// <param name="name">The name of the object.</param>
|
||||
// /// <param name="definition">
|
||||
// /// The object definition for the object that is to be instantiated.
|
||||
// /// </param>
|
||||
// /// <param name="arguments">
|
||||
// /// The arguments to use if creating a prototype using explicit arguments to
|
||||
// /// a static factory method. It is invalid to use a non-<see langword="null"/> arguments value
|
||||
// /// in any other case.
|
||||
// /// </param>
|
||||
// /// <returns>
|
||||
// /// A new instance of the object.
|
||||
// /// </returns>
|
||||
// /// <exception cref="Spring.Objects.ObjectsException">
|
||||
// /// In case of errors.
|
||||
// /// </exception>
|
||||
// /// <remarks>
|
||||
// /// <p>
|
||||
// /// Delegates to the
|
||||
// /// <see cref="Spring.Objects.Factory.Support.AbstractAutowireCapableObjectFactory.CreateObject (string,RootObjectDefinition,object[],bool)"/>
|
||||
// /// method version with the <c>allowEagerCaching</c> parameter set to <b>true</b>.
|
||||
// /// </p>
|
||||
// /// <p>
|
||||
// /// The object definition will already have been merged with the parent
|
||||
// /// definition in case of a child definition.
|
||||
// /// </p>
|
||||
// /// <p>
|
||||
// /// All the other methods in this class invoke this method, although objects
|
||||
// /// may be cached after being instantiated by this method. All object
|
||||
// /// instantiation within this class is performed by this method.
|
||||
// /// </p>
|
||||
// /// </remarks>
|
||||
// protected internal override object CreateObject(string name, RootObjectDefinition definition, object[] arguments)
|
||||
// {
|
||||
// return CreateObject(name, definition, arguments, true, false);
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// Create an object instance for the given object definition.
|
||||
// /// </summary>
|
||||
// /// <param name="name">The name of the object.</param>
|
||||
// /// <param name="definition">
|
||||
// /// The object definition for the object that is to be instantiated.
|
||||
// /// </param>
|
||||
// /// <param name="arguments">
|
||||
// /// The arguments to use if creating a prototype using explicit arguments to
|
||||
// /// a static factory method. It is invalid to use a non-<see langword="null"/> arguments value
|
||||
// /// in any other case.
|
||||
// /// </param>
|
||||
// /// <returns>
|
||||
// /// A new instance of the object.
|
||||
// /// </returns>
|
||||
// /// <exception cref="Spring.Objects.ObjectsException">
|
||||
// /// In case of errors.
|
||||
// /// </exception>
|
||||
// /// <remarks>
|
||||
// /// <p>
|
||||
// /// Delegates to the
|
||||
// /// <see cref="Spring.Objects.Factory.Support.AbstractAutowireCapableObjectFactory.CreateObject (string,RootObjectDefinition,object[],bool)"/>
|
||||
// /// method version with the <c>allowEagerCaching</c> parameter set to <b>true</b>.
|
||||
// /// </p>
|
||||
// /// <p>
|
||||
// /// The object definition will already have been merged with the parent
|
||||
// /// definition in case of a child definition.
|
||||
// /// </p>
|
||||
// /// <p>
|
||||
// /// All the other methods in this class invoke this method, although objects
|
||||
// /// may be cached after being instantiated by this method. All object
|
||||
// /// instantiation within this class is performed by this method.
|
||||
// /// </p>
|
||||
// /// </remarks>
|
||||
// protected internal override object CreateObject(string name, RootObjectDefinition definition, object[] arguments)
|
||||
// {
|
||||
// return CreateObject(name, definition, arguments, true, false);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Create an object instance for the given object definition.
|
||||
@@ -1221,7 +1221,7 @@ namespace Spring.Objects.Factory.Support
|
||||
if (unsatisfiedDependencies.Count > 0)
|
||||
{
|
||||
throw new UnsatisfiedDependencyException(definition.ResourceDescription, name, unsatisfiedDependencies[0].Name,
|
||||
"Set this property value or disable dependency checking for this object.");
|
||||
"Set this property value or disable dependency checking for this object.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1438,32 +1438,10 @@ namespace Spring.Objects.Factory.Support
|
||||
/// </param>
|
||||
protected override void DestroyObject(string name, object target)
|
||||
{
|
||||
log.Debug(m => m("Destroying dependant objects for object '{0}", name));
|
||||
|
||||
DestroyDependantObjects(name);
|
||||
|
||||
ApplyObjectPostProcessBeforeDestruction(target, name);
|
||||
|
||||
if (target is IDisposable)
|
||||
using (new DisposableObjectAdapter(target, name, GetMergedObjectDefinition(name, true), ObjectPostProcessors))
|
||||
{
|
||||
log.Debug(m => m(string.Format(CultureInfo.InvariantCulture, "Calling Dispose() on object with name '{0}'.", name)));
|
||||
|
||||
try
|
||||
{
|
||||
((IDisposable)target).Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Destroy() on object with name '" + name + "' threw an exception.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
RootObjectDefinition rootDefinition = GetMergedObjectDefinition(name, false);
|
||||
if (rootDefinition != null && StringUtils.HasText(rootDefinition.DestroyMethodName))
|
||||
{
|
||||
log.Debug(m => m("Calling custom destroy method '{0}' on object with name '{1}'.", rootDefinition.DestroyMethodName, name));
|
||||
|
||||
InvokeCustomDestroyMethod(name, target, rootDefinition.DestroyMethodName);
|
||||
log.Debug(m => m("Destroying dependant objects for object '{0}", name));
|
||||
DestroyDependantObjects(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1533,123 +1511,123 @@ namespace Spring.Objects.Factory.Support
|
||||
///// <param name="argumentValue">
|
||||
///// The value of the property that is being resolved.
|
||||
///// </param>
|
||||
// protected object ResolveValueIfNecessary(string name, RootObjectDefinition definition, string argumentName, object argumentValue)
|
||||
// {
|
||||
// object resolvedValue = null;
|
||||
//
|
||||
// AssertUtils.ArgumentNotNull(resolvedValue, "test");
|
||||
//
|
||||
// // we must check the argument value to see whether it requires a runtime
|
||||
// // reference to another object to be resolved.
|
||||
// // if it does, we'll attempt to instantiate the object and set the reference.
|
||||
// if (RemotingServices.IsTransparentProxy(argumentValue))
|
||||
// {
|
||||
// resolvedValue = argumentValue;
|
||||
// }
|
||||
// else if (argumentValue is ObjectDefinitionHolder)
|
||||
// {
|
||||
// // contains an IObjectDefinition with name and aliases...
|
||||
// ObjectDefinitionHolder holder = (ObjectDefinitionHolder)argumentValue;
|
||||
// resolvedValue = ResolveInnerObjectDefinition(name, holder.ObjectName, argumentName, holder.ObjectDefinition, definition.IsSingleton);
|
||||
// }
|
||||
// else if (argumentValue is IObjectDefinition)
|
||||
// {
|
||||
// // resolve plain IObjectDefinition, without contained name: use dummy name...
|
||||
// IObjectDefinition def = (IObjectDefinition)argumentValue;
|
||||
// resolvedValue = ResolveInnerObjectDefinition(name, "(inner object)", argumentName, def, definition.IsSingleton);
|
||||
//
|
||||
// }
|
||||
// else if (argumentValue is RuntimeObjectReference)
|
||||
// {
|
||||
// RuntimeObjectReference roref = (RuntimeObjectReference)argumentValue;
|
||||
// resolvedValue = ResolveReference(definition, name, argumentName, roref);
|
||||
// }
|
||||
// else if (argumentValue is ExpressionHolder)
|
||||
// {
|
||||
// ExpressionHolder expHolder = (ExpressionHolder)argumentValue;
|
||||
// object context = null;
|
||||
// IDictionary variables = null;
|
||||
//
|
||||
// if (expHolder.Properties != null)
|
||||
// {
|
||||
// PropertyValue contextProperty = expHolder.Properties.GetPropertyValue("Context");
|
||||
// context = contextProperty == null
|
||||
// ? null
|
||||
// : ResolveValueIfNecessary2(name, definition, "Context",
|
||||
// contextProperty.Value);
|
||||
// PropertyValue variablesProperty = expHolder.Properties.GetPropertyValue("Variables");
|
||||
// object vars = (variablesProperty == null
|
||||
// ? null
|
||||
// : ResolveValueIfNecessary2(name, definition, "Variables",
|
||||
// variablesProperty.Value));
|
||||
// if (vars is IDictionary)
|
||||
// {
|
||||
// variables = (IDictionary)vars;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (vars != null) throw new ArgumentException("'Variables' must resolve to an IDictionary");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (variables == null) variables = CollectionsUtil.CreateCaseInsensitiveHashtable();
|
||||
// // add 'this' objectfactory reference to variables
|
||||
// variables.Add(Expression.ReservedVariableNames.CurrentObjectFactory, this);
|
||||
//
|
||||
// resolvedValue = expHolder.Expression.GetValue(context, variables);
|
||||
// }
|
||||
// else if (argumentValue is IManagedCollection)
|
||||
// {
|
||||
// resolvedValue =
|
||||
// ((IManagedCollection)argumentValue).Resolve(name, definition, argumentName,
|
||||
// new ManagedCollectionElementResolver(ResolveValueIfNecessary2));
|
||||
// }
|
||||
// else if (argumentValue is TypedStringValue)
|
||||
// {
|
||||
// TypedStringValue tsv = (TypedStringValue)argumentValue;
|
||||
// try
|
||||
// {
|
||||
// Type resolvedTargetType = ResolveTargetType(tsv);
|
||||
// if (resolvedTargetType != null)
|
||||
// {
|
||||
// resolvedValue = TypeConversionUtils.ConvertValueIfNecessary(tsv.TargetType, tsv.Value, null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// resolvedValue = tsv.Value;
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// throw new ObjectCreationException(definition.ResourceDescription, name,
|
||||
// "Error converted typed String value for " + argumentName, ex);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // no need to resolve value...
|
||||
// resolvedValue = argumentValue;
|
||||
// }
|
||||
// return resolvedValue;
|
||||
// }
|
||||
// protected object ResolveValueIfNecessary(string name, RootObjectDefinition definition, string argumentName, object argumentValue)
|
||||
// {
|
||||
// object resolvedValue = null;
|
||||
//
|
||||
// AssertUtils.ArgumentNotNull(resolvedValue, "test");
|
||||
//
|
||||
// // we must check the argument value to see whether it requires a runtime
|
||||
// // reference to another object to be resolved.
|
||||
// // if it does, we'll attempt to instantiate the object and set the reference.
|
||||
// if (RemotingServices.IsTransparentProxy(argumentValue))
|
||||
// {
|
||||
// resolvedValue = argumentValue;
|
||||
// }
|
||||
// else if (argumentValue is ObjectDefinitionHolder)
|
||||
// {
|
||||
// // contains an IObjectDefinition with name and aliases...
|
||||
// ObjectDefinitionHolder holder = (ObjectDefinitionHolder)argumentValue;
|
||||
// resolvedValue = ResolveInnerObjectDefinition(name, holder.ObjectName, argumentName, holder.ObjectDefinition, definition.IsSingleton);
|
||||
// }
|
||||
// else if (argumentValue is IObjectDefinition)
|
||||
// {
|
||||
// // resolve plain IObjectDefinition, without contained name: use dummy name...
|
||||
// IObjectDefinition def = (IObjectDefinition)argumentValue;
|
||||
// resolvedValue = ResolveInnerObjectDefinition(name, "(inner object)", argumentName, def, definition.IsSingleton);
|
||||
//
|
||||
// }
|
||||
// else if (argumentValue is RuntimeObjectReference)
|
||||
// {
|
||||
// RuntimeObjectReference roref = (RuntimeObjectReference)argumentValue;
|
||||
// resolvedValue = ResolveReference(definition, name, argumentName, roref);
|
||||
// }
|
||||
// else if (argumentValue is ExpressionHolder)
|
||||
// {
|
||||
// ExpressionHolder expHolder = (ExpressionHolder)argumentValue;
|
||||
// object context = null;
|
||||
// IDictionary variables = null;
|
||||
//
|
||||
// if (expHolder.Properties != null)
|
||||
// {
|
||||
// PropertyValue contextProperty = expHolder.Properties.GetPropertyValue("Context");
|
||||
// context = contextProperty == null
|
||||
// ? null
|
||||
// : ResolveValueIfNecessary2(name, definition, "Context",
|
||||
// contextProperty.Value);
|
||||
// PropertyValue variablesProperty = expHolder.Properties.GetPropertyValue("Variables");
|
||||
// object vars = (variablesProperty == null
|
||||
// ? null
|
||||
// : ResolveValueIfNecessary2(name, definition, "Variables",
|
||||
// variablesProperty.Value));
|
||||
// if (vars is IDictionary)
|
||||
// {
|
||||
// variables = (IDictionary)vars;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (vars != null) throw new ArgumentException("'Variables' must resolve to an IDictionary");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (variables == null) variables = CollectionsUtil.CreateCaseInsensitiveHashtable();
|
||||
// // add 'this' objectfactory reference to variables
|
||||
// variables.Add(Expression.ReservedVariableNames.CurrentObjectFactory, this);
|
||||
//
|
||||
// resolvedValue = expHolder.Expression.GetValue(context, variables);
|
||||
// }
|
||||
// else if (argumentValue is IManagedCollection)
|
||||
// {
|
||||
// resolvedValue =
|
||||
// ((IManagedCollection)argumentValue).Resolve(name, definition, argumentName,
|
||||
// new ManagedCollectionElementResolver(ResolveValueIfNecessary2));
|
||||
// }
|
||||
// else if (argumentValue is TypedStringValue)
|
||||
// {
|
||||
// TypedStringValue tsv = (TypedStringValue)argumentValue;
|
||||
// try
|
||||
// {
|
||||
// Type resolvedTargetType = ResolveTargetType(tsv);
|
||||
// if (resolvedTargetType != null)
|
||||
// {
|
||||
// resolvedValue = TypeConversionUtils.ConvertValueIfNecessary(tsv.TargetType, tsv.Value, null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// resolvedValue = tsv.Value;
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// throw new ObjectCreationException(definition.ResourceDescription, name,
|
||||
// "Error converted typed String value for " + argumentName, ex);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // no need to resolve value...
|
||||
// resolvedValue = argumentValue;
|
||||
// }
|
||||
// return resolvedValue;
|
||||
// }
|
||||
|
||||
///// <summary>
|
||||
///// Resolve the target type of the passed <see cref="TypedStringValue"/>.
|
||||
///// </summary>
|
||||
///// <param name="value">The <see cref="TypedStringValue"/> who's target type is to be resolved</param>
|
||||
///// <returns>The resolved target type, if any. <see lang="null" /> otherwise.</returns>
|
||||
// protected virtual Type ResolveTargetType(TypedStringValue value)
|
||||
// {
|
||||
// if (value.HasTargetType)
|
||||
// {
|
||||
// return value.TargetType;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// protected virtual Type ResolveTargetType(TypedStringValue value)
|
||||
// {
|
||||
// if (value.HasTargetType)
|
||||
// {
|
||||
// return value.TargetType;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
///// <summary>
|
||||
///// Resolves an inner object definition.
|
||||
@@ -1674,30 +1652,30 @@ namespace Spring.Objects.Factory.Support
|
||||
///// <returns>
|
||||
///// The resolved object as defined by the inner object definition.
|
||||
///// </returns>
|
||||
// protected object ResolveInnerObjectDefinition(string name, string innerObjectName, string argumentName, IObjectDefinition definition,
|
||||
// bool singletonOwner)
|
||||
// {
|
||||
// RootObjectDefinition mod = GetMergedObjectDefinition(innerObjectName, definition);
|
||||
// mod.IsSingleton = singletonOwner;
|
||||
// object instance;
|
||||
// object result;
|
||||
// try
|
||||
// {
|
||||
// instance = InstantiateObject(innerObjectName, mod, ObjectUtils.EmptyObjects, false, false);
|
||||
// result = GetObjectForInstance(innerObjectName, instance);
|
||||
// }
|
||||
// catch (ObjectsException ex)
|
||||
// {
|
||||
// throw ObjectCreationException.GetObjectCreationException(ex, name, argumentName, definition.ResourceDescription, innerObjectName);
|
||||
// }
|
||||
// if (singletonOwner && instance is IDisposable)
|
||||
// {
|
||||
// // keep a reference to the inner object instance, to be able to destroy
|
||||
// // it on factory shutdown...
|
||||
// DisposableInnerObjects.Add(instance);
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
// protected object ResolveInnerObjectDefinition(string name, string innerObjectName, string argumentName, IObjectDefinition definition,
|
||||
// bool singletonOwner)
|
||||
// {
|
||||
// RootObjectDefinition mod = GetMergedObjectDefinition(innerObjectName, definition);
|
||||
// mod.IsSingleton = singletonOwner;
|
||||
// object instance;
|
||||
// object result;
|
||||
// try
|
||||
// {
|
||||
// instance = InstantiateObject(innerObjectName, mod, ObjectUtils.EmptyObjects, false, false);
|
||||
// result = GetObjectForInstance(innerObjectName, instance);
|
||||
// }
|
||||
// catch (ObjectsException ex)
|
||||
// {
|
||||
// throw ObjectCreationException.GetObjectCreationException(ex, name, argumentName, definition.ResourceDescription, innerObjectName);
|
||||
// }
|
||||
// if (singletonOwner && instance is IDisposable)
|
||||
// {
|
||||
// // keep a reference to the inner object instance, to be able to destroy
|
||||
// // it on factory shutdown...
|
||||
// DisposableInnerObjects.Add(instance);
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a reference to another object in the factory.
|
||||
@@ -1897,7 +1875,6 @@ namespace Spring.Objects.Factory.Support
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Applies the <code>PostProcessAfterInitialization</code> callback of all
|
||||
/// registered IObjectPostProcessors, giving them a chance to post-process
|
||||
|
||||
@@ -29,6 +29,7 @@ using System.ComponentModel;
|
||||
using Common.Logging;
|
||||
|
||||
using Spring.Collections;
|
||||
using Spring.Collections.Generic;
|
||||
using Spring.Core;
|
||||
using Spring.Core.TypeConversion;
|
||||
using Spring.Objects.Factory.Config;
|
||||
@@ -144,6 +145,27 @@ namespace Spring.Objects.Factory.Support
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, object> factoryObjectProductCache = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Disposable object instances: object name --> disposable instance
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, object> disposableObjects = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// root object definitons: object name --> Root Object Definition
|
||||
/// </summary>
|
||||
protected SynchronizedHashtable mergedObjectDefinitions = new Spring.Collections.SynchronizedHashtable();
|
||||
|
||||
/// <summary>
|
||||
/// Whether to cache object metadata or rather reobtain it for every access
|
||||
/// </summary>
|
||||
protected bool cacheObjectMetadata = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Names of object that have already been created at least once
|
||||
/// </summary>
|
||||
private Spring.Collections.Generic.ISet<string> alreadyCreated = new SynchronizedSet<string>(new HashedSet<string>());
|
||||
|
||||
#region Constructor (s) / Destructor
|
||||
|
||||
/// <summary>
|
||||
@@ -623,11 +645,26 @@ namespace Spring.Objects.Factory.Support
|
||||
/// Return a <see cref="Spring.Objects.Factory.Support.RootObjectDefinition"/>,
|
||||
/// even by traversing parent if the parameter is a child definition.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="od">The od.</param>
|
||||
/// <returns>
|
||||
/// A merged <see cref="Spring.Objects.Factory.Support.RootObjectDefinition"/>
|
||||
/// with overridden properties.
|
||||
/// </returns>
|
||||
protected internal virtual RootObjectDefinition GetMergedObjectDefinition(string name, IObjectDefinition od)
|
||||
{
|
||||
return GetMergedLocalObjectDefinition(name) ?? GetMergedObjectDefinitionInternal(name, od);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="Spring.Objects.Factory.Support.RootObjectDefinition"/>,
|
||||
/// even by traversing parent if the parameter is a child definition.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A merged <see cref="Spring.Objects.Factory.Support.RootObjectDefinition"/>
|
||||
/// with overridden properties.
|
||||
/// </returns>
|
||||
protected internal virtual RootObjectDefinition GetMergedObjectDefinitionInternal(string name, IObjectDefinition od)
|
||||
{
|
||||
if (od == null)
|
||||
{
|
||||
@@ -636,40 +673,99 @@ namespace Spring.Objects.Factory.Support
|
||||
|
||||
RootObjectDefinition mod;
|
||||
|
||||
if (od.ParentName == null)
|
||||
// Check with full lock now in order to enforce the same merged instance.
|
||||
lock (this.mergedObjectDefinitions.SyncRoot)
|
||||
{
|
||||
mod = CreateRootObjectDefinition(od);
|
||||
}
|
||||
else
|
||||
{
|
||||
// IObjectDefinition childDefinition = definition;
|
||||
IObjectDefinition pod = null;
|
||||
if (!name.Equals(od.ParentName))
|
||||
mod = this.mergedObjectDefinitions[name] as RootObjectDefinition;
|
||||
|
||||
if (null != mod)
|
||||
{
|
||||
pod = GetMergedObjectDefinition(TransformedObjectName(od.ParentName), true);
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
||||
if (od.ParentName == null)
|
||||
{
|
||||
mod = CreateRootObjectDefinition(od);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ParentObjectFactory is AbstractObjectFactory)
|
||||
IObjectDefinition pod = null;
|
||||
if (!name.Equals(od.ParentName))
|
||||
{
|
||||
pod = ((AbstractObjectFactory)ParentObjectFactory).GetMergedObjectDefinition(od.ParentName, true);
|
||||
pod = GetMergedObjectDefinition(TransformedObjectName(od.ParentName), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ParentObjectFactory is AbstractObjectFactory)
|
||||
{
|
||||
pod = ((AbstractObjectFactory)ParentObjectFactory).GetMergedObjectDefinition(
|
||||
od.ParentName, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (pod == null)
|
||||
{
|
||||
throw new NoSuchObjectDefinitionException(od.ParentName,
|
||||
string.Format(
|
||||
"Parent name '{0}' is equal to object name '{1}' - "
|
||||
+
|
||||
"cannot be resolved without an AbstractObjectFactory parent.",
|
||||
od.ParentName, name));
|
||||
}
|
||||
|
||||
mod = CreateRootObjectDefinition(pod);
|
||||
mod.OverrideFrom(od);
|
||||
}
|
||||
|
||||
if (pod == null)
|
||||
// Only cache the merged bean definition if we're already about to create an
|
||||
// instance of the object, or at least have already created an instance before.
|
||||
if (CacheObjectMetadata && IsObjectEligibleForMetadataCaching(name))
|
||||
{
|
||||
throw new NoSuchObjectDefinitionException(od.ParentName,
|
||||
string.Format(
|
||||
"Parent name '{0}' is equal to object name '{1}' - "
|
||||
+
|
||||
"cannot be resolved without an AbstractObjectFactory parent.",
|
||||
od.ParentName, name));
|
||||
this.mergedObjectDefinitions.Remove(name);
|
||||
this.mergedObjectDefinitions.Add(name, mod);
|
||||
}
|
||||
|
||||
mod = CreateRootObjectDefinition(pod);
|
||||
mod.OverrideFrom(od);
|
||||
return mod;
|
||||
} //release the lock scope
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the merged local object definition.
|
||||
/// </summary>
|
||||
/// <param name="objectName">Name of the object.</param>
|
||||
/// <returns>
|
||||
/// Merged RootBeanDefinition, traversing the parent bean definition
|
||||
/// if the specified bean corresponds to a child bean definition.
|
||||
/// </returns>
|
||||
protected RootObjectDefinition GetMergedLocalObjectDefinition(string objectName)
|
||||
{
|
||||
// Quick check on the concurrent map first, with minimal locking.
|
||||
RootObjectDefinition mbd = null;
|
||||
|
||||
if (this.mergedObjectDefinitions.ContainsKey(objectName))
|
||||
{
|
||||
mbd = this.mergedObjectDefinitions[objectName] as RootObjectDefinition;
|
||||
}
|
||||
return mod;
|
||||
|
||||
return mbd; // ?? GetMergedObjectDefinition(objectName, GetObjectDefinition(objectName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the metadata for the specified object name is eligible for caching.
|
||||
/// </summary>
|
||||
/// <param name="beanName">Name of the bean.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is object eligible for metadata caching] [the specified bean name]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
protected bool IsObjectEligibleForMetadataCaching(String beanName)
|
||||
{
|
||||
return this.alreadyCreated.Contains(beanName);
|
||||
}
|
||||
|
||||
protected bool CacheObjectMetadata
|
||||
{
|
||||
get { return this.cacheObjectMetadata; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1233,8 +1329,8 @@ namespace Spring.Objects.Factory.Support
|
||||
/// </summary>
|
||||
/// <remarks>More specifically, check whether a GetObject call for the given name
|
||||
/// would return an object that is assignable to the specified target type.
|
||||
/// Translates aliases back to the corresponding canonical bean name.
|
||||
/// Will ask the parent factory if the bean cannot be found in this factory instance.
|
||||
/// Translates aliases back to the corresponding canonical instance name.
|
||||
/// Will ask the parent factory if the instance cannot be found in this factory instance.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the object to query.</param>
|
||||
/// <param name="targetType">Type of the target to match against.</param>
|
||||
@@ -1314,8 +1410,8 @@ namespace Spring.Objects.Factory.Support
|
||||
/// </summary>
|
||||
/// <remarks>More specifically, check whether a GetObject call for the given name
|
||||
/// would return an object that is assignable to the specified target type.
|
||||
/// Translates aliases back to the corresponding canonical bean name.
|
||||
/// Will ask the parent factory if the bean cannot be found in this factory instance.
|
||||
/// Translates aliases back to the corresponding canonical instance name.
|
||||
/// Will ask the parent factory if the instance cannot be found in this factory instance.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the object to query.</param>
|
||||
/// <typeparam name="T">Type of the target to match against.</typeparam>
|
||||
@@ -1624,7 +1720,7 @@ namespace Spring.Objects.Factory.Support
|
||||
private OrderedDictionary singletonLocks;
|
||||
|
||||
/// <summary>
|
||||
/// Set of registered singletons, containing the bean names in registration order
|
||||
/// Set of registered singletons, containing the instance names in registration order
|
||||
/// </summary>
|
||||
private HashSet<string> registeredSingletons = new HashSet<string>();
|
||||
|
||||
@@ -1664,7 +1760,7 @@ namespace Spring.Objects.Factory.Support
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the local object factory contains a bean of the given name,
|
||||
/// Determines whether the local object factory contains a instance of the given name,
|
||||
/// ignoring object defined in ancestor contexts.
|
||||
/// This is an alternative to <code>ContainsObject</code>, ignoring an object
|
||||
/// of the given name from an ancestor object factory.
|
||||
@@ -2194,6 +2290,15 @@ namespace Spring.Objects.Factory.Support
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RegisterDisposableObjectIfNecessary(name, instance, mergedObjectDefinition);
|
||||
}
|
||||
catch (ObjectDefinitionValidationException ex)
|
||||
{
|
||||
throw new ObjectCreationException(mergedObjectDefinition.ResourceDescription, name, "Invalid destruction signature", ex);
|
||||
}
|
||||
|
||||
return EnsureObjectIsOfRequiredType(name, instance, requiredType);
|
||||
}
|
||||
catch
|
||||
@@ -2237,6 +2342,34 @@ namespace Spring.Objects.Factory.Support
|
||||
}
|
||||
}
|
||||
|
||||
protected void RegisterDisposableObjectIfNecessary(string name, object instance, RootObjectDefinition od)
|
||||
{
|
||||
if (od.IsSingleton && RequiresDestruction(instance, od))
|
||||
{
|
||||
// Register a DisposableObject implementation that performs all destruction
|
||||
// work for the given object: DestructionAwareObjectPostProcessors,
|
||||
// DisposableObject interface, custom destroy method.
|
||||
RegisterDisposableObject(name,
|
||||
new DisposableObjectAdapter(instance, name, od, objectPostProcessors));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requireses the destruction.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to check.</param>
|
||||
/// <param name="od">The corresponding instance definition.</param>
|
||||
/// <returns>
|
||||
/// Boolean indicating whether destruction is required.
|
||||
/// </returns>
|
||||
|
||||
protected bool RequiresDestruction(object instance, RootObjectDefinition od)
|
||||
{
|
||||
return (instance != null &&
|
||||
(instance is IDisposable || od.DestroyMethodName != null || HasDestructionAwareBeanPostProcessors));
|
||||
}
|
||||
|
||||
private int nestingCount;
|
||||
|
||||
/// <summary>
|
||||
@@ -2507,17 +2640,17 @@ namespace Spring.Objects.Factory.Support
|
||||
if (name == alias)
|
||||
{
|
||||
#region Instrumentation
|
||||
|
||||
|
||||
if (log.IsDebugEnabled)
|
||||
{
|
||||
log.Debug(string.Format("Ignoring attempt to Register alias '{0}' for object with name '{1}' because name and alias would be the same value.", alias, name));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#region Instrumentation
|
||||
|
||||
if (log.IsDebugEnabled)
|
||||
@@ -2597,7 +2730,7 @@ namespace Spring.Objects.Factory.Support
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Only checks already instantiated singletons; does not return names
|
||||
/// for singleton bean definitions which have not been instantiated yet.
|
||||
/// for singleton instance definitions which have not been instantiated yet.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The main purpose of this method is to check manually registered singletons
|
||||
@@ -2666,6 +2799,23 @@ namespace Spring.Objects.Factory.Support
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registers the disposable object.
|
||||
/// </summary>
|
||||
/// <param name="objectName">Name of the instance.</param>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// Add the given instance to the list of disposable beans in this registry.
|
||||
/// Disposable beans usually correspond to registered singletons,
|
||||
/// matching the instance name but potentially being a different instance
|
||||
/// (for example, a DisposableBean adapter for a singleton that does not
|
||||
/// naturally implement <see cref="IDisposable"/>).
|
||||
public void RegisterDisposableObject(String objectName, IDisposable instance)
|
||||
{
|
||||
disposableObjects.Add(objectName, instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given object name is already in use within this factory,
|
||||
/// i.e. whether there is a local object or alias registered under this name or
|
||||
|
||||
@@ -1287,7 +1287,7 @@ namespace Spring.Objects.Factory.Support
|
||||
fallbackObjectName = candidateBeanName;
|
||||
}
|
||||
}
|
||||
return (primaryObjectName != null ? primaryObjectName : fallbackObjectName);
|
||||
return (primaryObjectName ?? fallbackObjectName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Common.Logging;
|
||||
using Spring.Collections;
|
||||
using Spring.Objects.Factory.Config;
|
||||
using Spring.Util;
|
||||
|
||||
namespace Spring.Objects.Factory.Support
|
||||
{
|
||||
public class DisposableObjectAdapter : IDisposable
|
||||
{
|
||||
private readonly ILog logger = LogManager.GetLogger(typeof(DisposableObjectAdapter));
|
||||
|
||||
private object instance;
|
||||
|
||||
private string objectName;
|
||||
|
||||
private bool invokeDisposableObject;
|
||||
|
||||
private string destroyMethodName;
|
||||
|
||||
private MethodInfo destroyMethod;
|
||||
|
||||
private List<IDestructionAwareObjectPostProcessor> objectPostProcessors;
|
||||
|
||||
/// <summary>
|
||||
/// Disposables the bean adapter.
|
||||
/// </summary>
|
||||
/// <param name="instance">The bean.</param>
|
||||
/// <param name="objectName">Name of the bean.</param>
|
||||
/// <param name="objectDefinition">The bean definition.</param>
|
||||
/// <param name="postProcessors">The post processors.</param>
|
||||
/// <param name="acc">The acc.</param>
|
||||
/// Create a new DisposableBeanAdapter for the given bean.
|
||||
/// @param bean the bean instance (never
|
||||
/// <code>null</code>
|
||||
/// )
|
||||
/// @param beanName the name of the bean
|
||||
/// @param beanDefinition the merged bean definition
|
||||
/// @param postProcessors the List of BeanPostProcessors
|
||||
/// (potentially DestructionAwareBeanPostProcessor), if any
|
||||
|
||||
public DisposableObjectAdapter(object instance, string objectName, RootObjectDefinition objectDefinition, ISet postProcessors)
|
||||
{
|
||||
AssertUtils.ArgumentNotNull(instance, "Disposable object must not be null");
|
||||
|
||||
this.instance = instance;
|
||||
this.objectName = objectName;
|
||||
this.invokeDisposableObject = (this.instance is IDisposable); // && !beanDefinition.IsExternallyManagedDestroyMethod("destroy"));
|
||||
|
||||
if (null == objectDefinition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InferDestroyMethodIfNecessary(objectDefinition);
|
||||
|
||||
string definedDestroyMethodName = objectDefinition.DestroyMethodName;
|
||||
|
||||
if (definedDestroyMethodName != null && !(this.invokeDisposableObject && "Destroy".Equals(definedDestroyMethodName))) // && !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName))
|
||||
{
|
||||
this.destroyMethodName = definedDestroyMethodName;
|
||||
this.destroyMethod = DetermineDestroyMethod();
|
||||
if (this.destroyMethod == null)
|
||||
{
|
||||
//TODO: add support for Enforcing Destroy Method
|
||||
//if (beanDefinition.IsEnforceDestroyMethod()) {
|
||||
// throw new BeanDefinitionValidationException("Couldn't find a destroy method named '" +
|
||||
// destroyMethodName + "' on bean with name '" + beanName + "'");
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
Type[] paramTypes = ReflectionUtils.GetParameterTypes(this.destroyMethod);
|
||||
if (paramTypes.Length > 1)
|
||||
{
|
||||
throw new ObjectDefinitionValidationException("Method '" + definedDestroyMethodName + "' of object '" +
|
||||
objectName + "' has more than one parameter - not supported as Destroy Method");
|
||||
}
|
||||
else if (paramTypes.Length == 1 && !(paramTypes[0] == typeof(bool)))
|
||||
{
|
||||
throw new ObjectDefinitionValidationException("Method '" + definedDestroyMethodName + "' of object '" +
|
||||
objectName + "' has a non-boolean parameter - not supported as Destroy Method");
|
||||
}
|
||||
}
|
||||
}
|
||||
this.objectPostProcessors = FilterPostProcessors(postProcessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current value of the given beanDefinition's destroyMethodName property is
|
||||
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
|
||||
* Candidate methods are currently limited to public, no-arg methods named 'close'
|
||||
* (whether declared locally or inherited). The given beanDefinition's
|
||||
* destroyMethodName is updated to be null if no such method is found, otherwise set
|
||||
* to the name of the inferred method. This constant serves as the default for the
|
||||
* {@code @Bean#customDestroyMethod} attribute and the value of the constant may also be
|
||||
* used in XML within the {@code <bean destroy-method="">} or {@code
|
||||
* <beans default-destroy-method="">} attributes.
|
||||
*/
|
||||
private void InferDestroyMethodIfNecessary(RootObjectDefinition beanDefinition)
|
||||
{
|
||||
if ("(Inferred)".Equals(beanDefinition.DestroyMethodName))
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodInfo candidate = ReflectionUtils.GetMethod(instance.GetType(), "Close", null);
|
||||
if (candidate.IsPublic)
|
||||
{
|
||||
beanDefinition.DestroyMethodName = candidate.Name;
|
||||
}
|
||||
}
|
||||
catch (MissingMethodException ex)
|
||||
{
|
||||
// no candidate destroy method found
|
||||
beanDefinition.DestroyMethodName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the post processors.
|
||||
/// </summary>
|
||||
/// <param name="postProcessors">The post processors.</param>
|
||||
/// <returns></returns>
|
||||
/// Search for all DestructionAwareBeanPostProcessors in the List.
|
||||
/// @param postProcessors the List to search
|
||||
/// @return the filtered List of DestructionAwareBeanPostProcessors
|
||||
private List<IDestructionAwareObjectPostProcessor> FilterPostProcessors(ISet postProcessors)
|
||||
{
|
||||
List<IDestructionAwareObjectPostProcessor> filteredPostProcessors = null;
|
||||
if (postProcessors != null && postProcessors.Count != 0)
|
||||
{
|
||||
filteredPostProcessors = new List<IDestructionAwareObjectPostProcessor>(postProcessors.Count);
|
||||
filteredPostProcessors.AddRange(postProcessors.OfType<IDestructionAwareObjectPostProcessor>());
|
||||
}
|
||||
return filteredPostProcessors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.objectPostProcessors != null && this.objectPostProcessors.Count != 0)
|
||||
{
|
||||
foreach (IDestructionAwareObjectPostProcessor processor in this.objectPostProcessors)
|
||||
{
|
||||
try
|
||||
{
|
||||
processor.PostProcessBeforeDestruction(this.instance, this.objectName);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.ErrorFormat(
|
||||
string.Format("Error during execution of {0}.PostProcessBeforeDestruction for object {1}",
|
||||
processor.GetType().Name, this.objectName), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.invokeDisposableObject)
|
||||
{
|
||||
if (logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Debug("Invoking Dispose() on object with name '" + this.objectName + "'");
|
||||
}
|
||||
try
|
||||
{
|
||||
((IDisposable)instance).Dispose();
|
||||
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
string msg = "Invocation of Dispose method failed on object with name '" + this.objectName + "'";
|
||||
if (logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Warn(msg, ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn(msg + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.destroyMethod != null)
|
||||
{
|
||||
InvokeCustomDestroyMethod(this.destroyMethod);
|
||||
}
|
||||
else if (this.destroyMethodName != null)
|
||||
{
|
||||
MethodInfo methodToCall = DetermineDestroyMethod();
|
||||
if (methodToCall != null)
|
||||
{
|
||||
InvokeCustomDestroyMethod(methodToCall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MethodInfo DetermineDestroyMethod()
|
||||
{
|
||||
try
|
||||
{
|
||||
return FindDestroyMethod();
|
||||
}
|
||||
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
throw new ObjectDefinitionValidationException("Couldn't find a unique Destroy Method on object with name '" +
|
||||
this.objectName + ": " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo FindDestroyMethod()
|
||||
{
|
||||
try
|
||||
{
|
||||
return ReflectionUtils.GetMethod(instance.GetType(), this.destroyMethodName, null);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the custom destroy method.
|
||||
/// </summary>
|
||||
/// <param name="customDestroyMethod">The custom destroy method.</param>
|
||||
/// Invoke the specified custom destroy method on the given bean.
|
||||
/// This implementation invokes a no-arg method if found, else checking
|
||||
/// for a method with a single boolean argument (passing in "true",
|
||||
/// assuming a "force" parameter), else logging an error.
|
||||
private void InvokeCustomDestroyMethod(MethodInfo customDestroyMethod)
|
||||
{
|
||||
Type[] paramTypes = ReflectionUtils.GetParameterTypes(customDestroyMethod);
|
||||
object[] args = new object[paramTypes.Length];
|
||||
if (paramTypes.Length == 1)
|
||||
{
|
||||
args[0] = true;
|
||||
}
|
||||
if (logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Debug("Invoking destroy method '" + this.destroyMethodName +
|
||||
"' on object with name '" + this.objectName + "'");
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
customDestroyMethod.Invoke(instance, args);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
string msg = "Invocation of destroy method '" + this.destroyMethodName +
|
||||
"' failed on object with name '" + this.objectName + "'";
|
||||
if (logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Warn(msg, ex.InnerException);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn(msg + ": " + ex.InnerException);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error("Couldn't invoke destroy method '" + this.destroyMethodName +
|
||||
"' on object with name '" + this.objectName + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Serializes a copy of the state of this class,
|
||||
* filtering out non-serializable BeanPostProcessors.
|
||||
*/
|
||||
//protected Object writeReplace() {
|
||||
// List<DestructionAwareBeanPostProcessor> serializablePostProcessors = null;
|
||||
// if (this.objectPostProcessors != null) {
|
||||
// serializablePostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>();
|
||||
// for (DestructionAwareBeanPostProcessor postProcessor : this.objectPostProcessors) {
|
||||
// if (postProcessor instanceof Serializable) {
|
||||
// serializablePostProcessors.add(postProcessor);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableObject,
|
||||
// this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -723,6 +723,7 @@
|
||||
<Compile Include="Objects\Factory\Parsing\ReaderContext.cs" />
|
||||
<Compile Include="Objects\Factory\Support\AutowireCandidateQualifier.cs" />
|
||||
<Compile Include="Objects\Factory\Support\DelegateInvokingFactoryObject.cs" />
|
||||
<Compile Include="Objects\Factory\Support\DisposableObjectAdapter.cs" />
|
||||
<Compile Include="Objects\Factory\Support\IObjectDefinitionRegistryPostProcessor.cs" />
|
||||
<Compile Include="Objects\Factory\Support\ObjectScope.cs" />
|
||||
<Compile Include="Objects\IObjectMetadataElement.cs" />
|
||||
|
||||
@@ -160,9 +160,9 @@ namespace Spring.Util
|
||||
Type[] parameterTypes = Array.ConvertAll<ParameterInfo, Type>(candidate.GetParameters(), delegate(ParameterInfo i) { return i.ParameterType; });
|
||||
bool typesMatch = false;
|
||||
|
||||
bool zeroTypeArguments = argumentTypes.Length == 0;
|
||||
bool zeroTypeArguments = null == argumentTypes || argumentTypes.Length == 0;
|
||||
|
||||
if (parameterTypes.Length == argumentTypes.Length && !zeroTypeArguments)
|
||||
if (!zeroTypeArguments && parameterTypes.Length == argumentTypes.Length)
|
||||
{
|
||||
for (int i = 0; i < parameterTypes.Length; i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user