// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; namespace Microsoft.VisualStudio.TestTools.UnitTesting { /// /// This class represents the live NON public INTERNAL object in the system /// public class PrivateObject { // bind everything private const BindingFlags BindToEveryThing = BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public; private static BindingFlags constructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.NonPublic; private object target; // automatically initialized to null private Type originalType; // automatically initialized to null private Dictionary> methodCache; // automatically initialized to null /// /// Initializes a new instance of the class that contains /// the already existing object of the private class /// /// object that serves as starting point to reach the private members /// the derefrencing string using . that points to the object to be retrived as in m_X.m_Y.m_Z public PrivateObject(object obj, string memberToAccess) { ValidateAccessString(memberToAccess); PrivateObject temp = obj as PrivateObject; if (temp == null) { temp = new PrivateObject(obj); } // Split The access string string[] arr = memberToAccess.Split(new char[] { '.' }); for (int i = 0; i < arr.Length; i++) { object next = temp.InvokeHelper(arr[i], BindToEveryThing | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.GetProperty, null, CultureInfo.InvariantCulture); temp = new PrivateObject(next); } this.target = temp.target; this.originalType = temp.originalType; } /// /// Initializes a new instance of the class that wraps the /// specified type. /// /// Name of the assembly /// fully qualified name /// Argmenets to pass to the constructor public PrivateObject(string assemblyName, string typeName, params object[] args) : this(assemblyName, typeName, null, args) { } /// /// Initializes a new instance of the class that wraps the /// specified type. /// /// Name of the assembly /// fully qualified name /// An array of objects representing the number, order, and type of the parameters for the constructor to get /// Argmenets to pass to the constructor public PrivateObject(string assemblyName, string typeName, Type[] parameterTypes, object[] args) : this(Type.GetType(string.Format(CultureInfo.InvariantCulture, "{0}, {1}", typeName, assemblyName), false), parameterTypes, args) { } /// /// Initializes a new instance of the class that wraps the /// specified type. /// /// type of the object to create /// Argmenets to pass to the constructor public PrivateObject(Type type, params object[] args) : this(type, null, args) { } /// /// Initializes a new instance of the class that wraps the /// specified type. /// /// type of the object to create /// An array of objects representing the number, order, and type of the parameters for the constructor to get /// Argmenets to pass to the constructor public PrivateObject(Type type, Type[] parameterTypes, object[] args) { object o; if (parameterTypes != null) { ConstructorInfo ci = type.GetConstructor(BindToEveryThing, null, parameterTypes, null); if (ci == null) { throw new ArgumentException("The constructor with the specified signature could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor."); } try { o = ci.Invoke(args); } catch (TargetInvocationException e) { Debug.Assert(e.InnerException != null, "Inner exception should not be null."); if (e.InnerException != null) { throw e.InnerException; } throw; } } else { o = Activator.CreateInstance(type, constructorFlags, null, args, null); } this.ConstructFrom(o); } /// /// Initializes a new instance of the class that wraps /// the given object. /// /// object to wrap public PrivateObject(object obj) { this.ConstructFrom(obj); } /// /// Initializes a new instance of the class that wraps /// the given object. /// /// object to wrap /// PrivateType object public PrivateObject(object obj, PrivateType type) { this.target = obj; this.originalType = type.ReferencedType; } /// /// Gets or sets the target /// public object Target { get { return this.target; } set { this.target = value; this.originalType = value.GetType(); } } /// /// Gets the type of underlying object /// public Type RealType { get { return this.originalType; } } private Dictionary> GenericMethodCache { get { if (this.methodCache == null) { this.BuildGenericMethodCacheForType(this.originalType); } Debug.Assert(this.methodCache != null, "Invalid method cache for type."); return this.methodCache; } } /// /// returns the hash code of the target object /// /// int representing hashcode of the target object public override int GetHashCode() { Debug.Assert(this.target != null, "target should not be null."); return this.target.GetHashCode(); } /// /// Equals /// /// Object with whom to compare /// returns true if the objects are equal. public override bool Equals(object obj) { if (this != obj) { Debug.Assert(this.target != null, "target should not be null."); if (typeof(PrivateObject) == obj?.GetType()) { return this.target.Equals(((PrivateObject)obj).target); } else { return false; } } return true; } /// /// Invokes the specified method /// /// Name of the method /// Arguments to pass to the member to invoke. /// Result of method call public object Invoke(string name, params object[] args) { return this.Invoke(name, null, args, CultureInfo.InvariantCulture); } /// /// Invokes the specified method /// /// Name of the method /// An array of objects representing the number, order, and type of the parameters for the method to get. /// Arguments to pass to the member to invoke. /// Result of method call public object Invoke(string name, Type[] parameterTypes, object[] args) { return this.Invoke(name, parameterTypes, args, CultureInfo.InvariantCulture); } /// /// Invokes the specified method /// /// Name of the method /// An array of objects representing the number, order, and type of the parameters for the method to get. /// Arguments to pass to the member to invoke. /// An array of types corresponding to the types of the generic arguments. /// Result of method call public object Invoke(string name, Type[] parameterTypes, object[] args, Type[] typeArguments) { return this.Invoke(name, BindToEveryThing, parameterTypes, args, CultureInfo.InvariantCulture, typeArguments); } /// /// Invokes the specified method /// /// Name of the method /// Arguments to pass to the member to invoke. /// Culture info /// Result of method call public object Invoke(string name, object[] args, CultureInfo culture) { return this.Invoke(name, null, args, culture); } /// /// Invokes the specified method /// /// Name of the method /// An array of objects representing the number, order, and type of the parameters for the method to get. /// Arguments to pass to the member to invoke. /// Culture info /// Result of method call public object Invoke(string name, Type[] parameterTypes, object[] args, CultureInfo culture) { return this.Invoke(name, BindToEveryThing, parameterTypes, args, culture); } /// /// Invokes the specified method /// /// Name of the method /// A bitmask comprised of one or more that specify how the search is conducted. /// Arguments to pass to the member to invoke. /// Result of method call public object Invoke(string name, BindingFlags bindingFlags, params object[] args) { return this.Invoke(name, bindingFlags, null, args, CultureInfo.InvariantCulture); } /// /// Invokes the specified method /// /// Name of the method /// A bitmask comprised of one or more that specify how the search is conducted. /// An array of objects representing the number, order, and type of the parameters for the method to get. /// Arguments to pass to the member to invoke. /// Result of method call public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) { return this.Invoke(name, bindingFlags, parameterTypes, args, CultureInfo.InvariantCulture); } /// /// Invokes the specified method /// /// Name of the method /// A bitmask comprised of one or more that specify how the search is conducted. /// Arguments to pass to the member to invoke. /// Culture info /// Result of method call public object Invoke(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) { return this.Invoke(name, bindingFlags, null, args, culture); } /// /// Invokes the specified method /// /// Name of the method /// A bitmask comprised of one or more that specify how the search is conducted. /// An array of objects representing the number, order, and type of the parameters for the method to get. /// Arguments to pass to the member to invoke. /// Culture info /// Result of method call public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture) { return this.Invoke(name, bindingFlags, parameterTypes, args, culture, null); } /// /// Invokes the specified method /// /// Name of the method /// A bitmask comprised of one or more that specify how the search is conducted. /// An array of objects representing the number, order, and type of the parameters for the method to get. /// Arguments to pass to the member to invoke. /// Culture info /// An array of types corresponding to the types of the generic arguments. /// Result of method call public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture, Type[] typeArguments) { if (parameterTypes != null) { bindingFlags |= BindToEveryThing | BindingFlags.Instance; // Fix up the parameter types MethodInfo member = this.originalType.GetMethod(name, bindingFlags, null, parameterTypes, null); // If the method was not found and type arguments were provided for generic paramaters, // attempt to look up a generic method. if ((member == null) && (typeArguments != null)) { // This method may contain generic parameters...if so, the previous call to // GetMethod() will fail because it doesn't fully support generic parameters. // Look in the method cache to see if there is a generic method // on the incoming type that contains the correct signature. member = this.GetGenericMethodFromCache(name, parameterTypes, typeArguments, bindingFlags, null); } if (member == null) { throw new ArgumentException( string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name)); } try { if (member.IsGenericMethodDefinition) { MethodInfo constructed = member.MakeGenericMethod(typeArguments); return constructed.Invoke(this.target, bindingFlags, null, args, culture); } else { return member.Invoke(this.target, bindingFlags, null, args, culture); } } catch (TargetInvocationException e) { Debug.Assert(e.InnerException != null, "Inner exception should not be null."); if (e.InnerException != null) { throw e.InnerException; } throw; } } else { return this.InvokeHelper(name, bindingFlags | BindingFlags.InvokeMethod, args, culture); } } /// /// Gets the array element using array of subsrcipts for each dimension /// /// Name of the member /// the indices of array /// An arrya of elements. public object GetArrayElement(string name, params int[] indices) { return this.GetArrayElement(name, BindToEveryThing, indices); } /// /// Sets the array element using array of subsrcipts for each dimension /// /// Name of the member /// Value to set /// the indices of array public void SetArrayElement(string name, object value, params int[] indices) { this.SetArrayElement(name, BindToEveryThing, value, indices); } /// /// Gets the array element using array of subsrcipts for each dimension /// /// Name of the member /// A bitmask comprised of one or more that specify how the search is conducted. /// the indices of array /// An arrya of elements. public object GetArrayElement(string name, BindingFlags bindingFlags, params int[] indices) { Array arr = (Array)this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); return arr.GetValue(indices); } /// /// Sets the array element using array of subsrcipts for each dimension /// /// Name of the member /// A bitmask comprised of one or more that specify how the search is conducted. /// Value to set /// the indices of array public void SetArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices) { Array arr = (Array)this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); arr.SetValue(value, indices); } /// /// Get the field /// /// Name of the field /// The field. public object GetField(string name) { return this.GetField(name, BindToEveryThing); } /// /// Sets the field /// /// Name of the field /// value to set public void SetField(string name, object value) { this.SetField(name, BindToEveryThing, value); } /// /// Gets the field /// /// Name of the field /// A bitmask comprised of one or more that specify how the search is conducted. /// The field. public object GetField(string name, BindingFlags bindingFlags) { return this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); } /// /// Sets the field /// /// Name of the field /// A bitmask comprised of one or more that specify how the search is conducted. /// value to set public void SetField(string name, BindingFlags bindingFlags, object value) { this.InvokeHelper(name, BindingFlags.SetField | bindingFlags, new object[] { value }, CultureInfo.InvariantCulture); } /// /// Get the field or property /// /// Name of the field or property /// The field or property. public object GetFieldOrProperty(string name) { return this.GetFieldOrProperty(name, BindToEveryThing); } /// /// Sets the field or property /// /// Name of the field or property /// value to set public void SetFieldOrProperty(string name, object value) { this.SetFieldOrProperty(name, BindToEveryThing, value); } /// /// Gets the field or property /// /// Name of the field or property /// A bitmask comprised of one or more that specify how the search is conducted. /// The field or property. public object GetFieldOrProperty(string name, BindingFlags bindingFlags) { return this.InvokeHelper(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture); } /// /// Sets the field or property /// /// Name of the field or property /// A bitmask comprised of one or more that specify how the search is conducted. /// value to set public void SetFieldOrProperty(string name, BindingFlags bindingFlags, object value) { this.InvokeHelper(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags, new object[] { value }, CultureInfo.InvariantCulture); } /// /// Gets the property /// /// Name of the property /// Arguments to pass to the member to invoke. /// The property. public object GetProperty(string name, params object[] args) { return this.GetProperty(name, null, args); } /// /// Gets the property /// /// Name of the property /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// Arguments to pass to the member to invoke. /// The property. public object GetProperty(string name, Type[] parameterTypes, object[] args) { return this.GetProperty(name, BindToEveryThing, parameterTypes, args); } /// /// Set the property /// /// Name of the property /// value to set /// Arguments to pass to the member to invoke. public void SetProperty(string name, object value, params object[] args) { this.SetProperty(name, null, value, args); } /// /// Set the property /// /// Name of the property /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// value to set /// Arguments to pass to the member to invoke. public void SetProperty(string name, Type[] parameterTypes, object value, object[] args) { this.SetProperty(name, BindToEveryThing, value, parameterTypes, args); } /// /// Gets the property /// /// Name of the property /// A bitmask comprised of one or more that specify how the search is conducted. /// Arguments to pass to the member to invoke. /// The property. public object GetProperty(string name, BindingFlags bindingFlags, params object[] args) { return this.GetProperty(name, bindingFlags, null, args); } /// /// Gets the property /// /// Name of the property /// A bitmask comprised of one or more that specify how the search is conducted. /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// Arguments to pass to the member to invoke. /// The property. public object GetProperty(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) { if (parameterTypes != null) { PropertyInfo pi = this.originalType.GetProperty(name, bindingFlags, null, null, parameterTypes, null); if (pi == null) { throw new ArgumentException( string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name)); } return pi.GetValue(this.target, args); } else { return this.InvokeHelper(name, bindingFlags | BindingFlags.GetProperty, args, null); } } /// /// Sets the property /// /// Name of the property /// A bitmask comprised of one or more that specify how the search is conducted. /// value to set /// Arguments to pass to the member to invoke. public void SetProperty(string name, BindingFlags bindingFlags, object value, params object[] args) { this.SetProperty(name, bindingFlags, value, null, args); } /// /// Sets the property /// /// Name of the property /// A bitmask comprised of one or more that specify how the search is conducted. /// value to set /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// Arguments to pass to the member to invoke. public void SetProperty(string name, BindingFlags bindingFlags, object value, Type[] parameterTypes, object[] args) { if (parameterTypes != null) { PropertyInfo pi = this.originalType.GetProperty(name, bindingFlags, null, null, parameterTypes, null); if (pi == null) { throw new ArgumentException( string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name)); } pi.SetValue(this.target, value, args); } else { object[] pass = new object[(args?.Length ?? 0) + 1]; pass[0] = value; args?.CopyTo(pass, 1); this.InvokeHelper(name, bindingFlags | BindingFlags.SetProperty, pass, null); } } /// /// Validate access string /// /// access string private static void ValidateAccessString(string access) { if (access.Length == 0) { throw new ArgumentException("Access string has invalid syntax."); } string[] arr = access.Split('.'); foreach (string str in arr) { if ((str.Length == 0) || (str.IndexOfAny(new char[] { ' ', '\t', '\n' }) != -1)) { throw new ArgumentException("Access string has invalid syntax."); } } } /// /// Invokes the memeber /// /// Name of the member /// Additional attributes /// Arguments for the invocation /// Culture /// Result of the invocation private object InvokeHelper(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) { Debug.Assert(this.target != null, "Internal Error: Null reference is returned for internal object"); // Invoke the actual Method try { return this.originalType.InvokeMember(name, bindingFlags, null, this.target, args, culture); } catch (TargetInvocationException e) { Debug.Assert(e.InnerException != null, "Inner exception should not be null."); if (e.InnerException != null) { throw e.InnerException; } throw; } } private void ConstructFrom(object obj) { this.target = obj; this.originalType = obj.GetType(); } private void BuildGenericMethodCacheForType(Type t) { Debug.Assert(t != null, "type should not be null."); this.methodCache = new Dictionary>(); MethodInfo[] members = t.GetMethods(BindToEveryThing); LinkedList listByName; // automatically initialized to null foreach (MethodInfo member in members) { if (member.IsGenericMethod || member.IsGenericMethodDefinition) { if (!this.GenericMethodCache.TryGetValue(member.Name, out listByName)) { listByName = new LinkedList(); this.GenericMethodCache.Add(member.Name, listByName); } Debug.Assert(listByName != null, "list should not be null."); listByName.AddLast(member); } } } /// /// Extracts the most appropriate generic method signature from the current private type. /// /// The name of the method in which to search the signature cache. /// An array of types corresponding to the types of the parameters in which to search. /// An array of types corresponding to the types of the generic arguments. /// to further filter the method signatures. /// Modifiers for parameters. /// A methodinfo instance. private MethodInfo GetGenericMethodFromCache(string methodName, Type[] parameterTypes, Type[] typeArguments, BindingFlags bindingFlags, ParameterModifier[] modifiers) { Debug.Assert(!string.IsNullOrEmpty(methodName), "Invalid method name."); Debug.Assert(parameterTypes != null, "Invalid parameter type array."); Debug.Assert(typeArguments != null, "Invalid type arguments array."); // Build a preliminary list of method candidates that contain roughly the same signature. var methodCandidates = this.GetMethodCandidates(methodName, parameterTypes, typeArguments, bindingFlags, modifiers); // Search of ambiguous methods (methods with the same signature). MethodInfo[] finalCandidates = new MethodInfo[methodCandidates.Count]; methodCandidates.CopyTo(finalCandidates, 0); if ((parameterTypes != null) && (parameterTypes.Length == 0)) { for (int i = 0; i < finalCandidates.Length; i++) { MethodInfo methodInfo = finalCandidates[i]; if (!RuntimeTypeHelper.CompareMethodSigAndName(methodInfo, finalCandidates[0])) { throw new AmbiguousMatchException(); } } // All the methods have the exact same name and sig so return the most derived one. return RuntimeTypeHelper.FindMostDerivedNewSlotMeth(finalCandidates, finalCandidates.Length) as MethodInfo; } // Now that we have a preliminary list of candidates, select the most appropriate one. return RuntimeTypeHelper.SelectMethod(bindingFlags, finalCandidates, parameterTypes, modifiers) as MethodInfo; } private LinkedList GetMethodCandidates(string methodName, Type[] parameterTypes, Type[] typeArguments, BindingFlags bindingFlags, ParameterModifier[] modifiers) { Debug.Assert(!string.IsNullOrEmpty(methodName), "methodName should not be null."); Debug.Assert(parameterTypes != null, "parameterTypes should not be null."); Debug.Assert(typeArguments != null, "typeArguments should not be null."); LinkedList methodCandidates = new LinkedList(); LinkedList methods = null; if (!this.GenericMethodCache.TryGetValue(methodName, out methods)) { return methodCandidates; } Debug.Assert(methods != null, "methods should not be null."); foreach (MethodInfo candidate in methods) { bool paramMatch = true; ParameterInfo[] candidateParams = null; Type[] genericArgs = candidate.GetGenericArguments(); Type sourceParameterType = null; if (genericArgs.Length != typeArguments.Length) { continue; } // Since we can't just get the correct MethodInfo from Reflection, // we will just match the number of parameters, their order, and their type var methodCandidate = candidate; candidateParams = methodCandidate.GetParameters(); if (candidateParams.Length != parameterTypes.Length) { continue; } // Exact binding if ((bindingFlags & BindingFlags.ExactBinding) != 0) { int i = 0; foreach (ParameterInfo candidateParam in candidateParams) { sourceParameterType = parameterTypes[i++]; if (candidateParam.ParameterType.ContainsGenericParameters) { // Since we have a generic parameter here, just make sure the IsArray matches. if (candidateParam.ParameterType.IsArray != sourceParameterType.IsArray) { paramMatch = false; break; } } else { if (candidateParam.ParameterType != sourceParameterType) { paramMatch = false; break; } } } if (paramMatch) { methodCandidates.AddLast(methodCandidate); continue; } } else { methodCandidates.AddLast(methodCandidate); } } return methodCandidates; } } /// /// This class represents a private class for the Private Accessor functionality. /// public class PrivateType { /// /// Binds to everything /// private const BindingFlags BindToEveryThing = BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; /// /// The wrapped type. /// private Type type; /// /// Initializes a new instance of the class that contains the private type. /// /// Assembly name /// fully qualified name of the public PrivateType(string assemblyName, string typeName) { Assembly asm = Assembly.Load(assemblyName); this.type = asm.GetType(typeName, true); } /// /// Initializes a new instance of the class that contains /// the private type from the type object /// /// The wrapped Type to create. public PrivateType(Type type) { if (type == null) { throw new ArgumentNullException("type"); } this.type = type; } /// /// Gets the referenced type /// public Type ReferencedType => this.type; /// /// Invokes static member /// /// Name of the member to InvokeHelper /// Arguements to the invoction /// Result of invocation public object InvokeStatic(string name, params object[] args) { return this.InvokeStatic(name, null, args, CultureInfo.InvariantCulture); } /// /// Invokes static member /// /// Name of the member to InvokeHelper /// An array of objects representing the number, order, and type of the parameters for the method to invoke /// Arguements to the invoction /// Result of invocation public object InvokeStatic(string name, Type[] parameterTypes, object[] args) { return this.InvokeStatic(name, parameterTypes, args, CultureInfo.InvariantCulture); } /// /// Invokes static member /// /// Name of the member to InvokeHelper /// An array of objects representing the number, order, and type of the parameters for the method to invoke /// Arguements to the invoction /// An array of types corresponding to the types of the generic arguments. /// Result of invocation public object InvokeStatic(string name, Type[] parameterTypes, object[] args, Type[] typeArguments) { return this.InvokeStatic(name, BindToEveryThing, parameterTypes, args, CultureInfo.InvariantCulture, typeArguments); } /// /// Invokes the static method /// /// Name of the member /// Arguements to the invocation /// Culture /// Result of invocation public object InvokeStatic(string name, object[] args, CultureInfo culture) { return this.InvokeStatic(name, null, args, culture); } /// /// Invokes the static method /// /// Name of the member /// An array of objects representing the number, order, and type of the parameters for the method to invoke /// Arguements to the invocation /// Culture info /// Result of invocation public object InvokeStatic(string name, Type[] parameterTypes, object[] args, CultureInfo culture) { return this.InvokeStatic(name, BindingFlags.InvokeMethod, parameterTypes, args, culture); } /// /// Invokes the static method /// /// Name of the member /// Additional invocation attributes /// Arguements to the invocation /// Result of invocation public object InvokeStatic(string name, BindingFlags bindingFlags, params object[] args) { return this.InvokeStatic(name, bindingFlags, null, args, CultureInfo.InvariantCulture); } /// /// Invokes the static method /// /// Name of the member /// Additional invocation attributes /// An array of objects representing the number, order, and type of the parameters for the method to invoke /// Arguements to the invocation /// Result of invocation public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) { return this.InvokeStatic(name, bindingFlags, parameterTypes, args, CultureInfo.InvariantCulture); } /// /// Invokes the static method /// /// Name of the member /// Additional invocation attributes /// Arguements to the invocation /// Culture /// Result of invocation public object InvokeStatic(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) { return this.InvokeStatic(name, bindingFlags, null, args, culture); } /// /// Invokes the static method /// /// Name of the member /// Additional invocation attributes /// /// An array of objects representing the number, order, and type of the parameters for the method to invoke /// Arguements to the invocation /// Culture /// Result of invocation public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture) { return this.InvokeStatic(name, bindingFlags, parameterTypes, args, culture, null); } /// /// Invokes the static method /// /// Name of the member /// Additional invocation attributes /// /// An array of objects representing the number, order, and type of the parameters for the method to invoke /// Arguements to the invocation /// Culture /// An array of types corresponding to the types of the generic arguments. /// Result of invocation public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture, Type[] typeArguments) { if (parameterTypes != null) { MethodInfo member = this.type.GetMethod(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, parameterTypes, null); if (member == null) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name)); } try { if (member.IsGenericMethodDefinition) { MethodInfo constructed = member.MakeGenericMethod(typeArguments); return constructed.Invoke(null, bindingFlags, null, args, culture); } else { return member.Invoke(null, bindingFlags, null, args, culture); } } catch (TargetInvocationException e) { Debug.Assert(e.InnerException != null, "Inner Exception should not be null."); if (e.InnerException != null) { throw e.InnerException; } throw; } } else { return this.InvokeHelperStatic(name, bindingFlags | BindingFlags.InvokeMethod, args, culture); } } /// /// Gets the element in static array /// /// Name of the array /// /// A one-dimensional array of 32-bit integers that represent the indexes specifying /// the position of the element to get. For instance, to access a[10][11] the indices would be {10,11} /// /// element at the specified location public object GetStaticArrayElement(string name, params int[] indices) { return this.GetStaticArrayElement(name, BindToEveryThing, indices); } /// /// Sets the memeber of the static array /// /// Name of the array /// value to set /// /// A one-dimensional array of 32-bit integers that represent the indexes specifying /// the position of the element to set. For instance, to access a[10][11] the array would be {10,11} /// public void SetStaticArrayElement(string name, object value, params int[] indices) { this.SetStaticArrayElement(name, BindToEveryThing, value, indices); } /// /// Gets the element in satatic array /// /// Name of the array /// Additional InvokeHelper attributes /// /// A one-dimensional array of 32-bit integers that represent the indexes specifying /// the position of the element to get. For instance, to access a[10][11] the array would be {10,11} /// /// element at the spcified location public object GetStaticArrayElement(string name, BindingFlags bindingFlags, params int[] indices) { Array arr = (Array)this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture); return arr.GetValue(indices); } /// /// Sets the memeber of the static array /// /// Name of the array /// Additional InvokeHelper attributes /// value to set /// /// A one-dimensional array of 32-bit integers that represent the indexes specifying /// the position of the element to set. For instance, to access a[10][11] the array would be {10,11} /// public void SetStaticArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices) { Array arr = (Array)this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); arr.SetValue(value, indices); } /// /// Gets the static field /// /// Name of the field /// The static field. public object GetStaticField(string name) { return this.GetStaticField(name, BindToEveryThing); } /// /// Sets the static field /// /// Name of the field /// Arguement to the invocation public void SetStaticField(string name, object value) { this.SetStaticField(name, BindToEveryThing, value); } /// /// Gets the static field using specified InvokeHelper attributes /// /// Name of the field /// Additional invocation attributes /// The static field. public object GetStaticField(string name, BindingFlags bindingFlags) { return this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); } /// /// Sets the static field using binding attributes /// /// Name of the field /// Additional InvokeHelper attributes /// Arguement to the invocation public void SetStaticField(string name, BindingFlags bindingFlags, object value) { this.InvokeHelperStatic(name, BindingFlags.SetField | bindingFlags | BindingFlags.Static, new[] { value }, CultureInfo.InvariantCulture); } /// /// Gets the static field or property /// /// Name of the field or property /// The static field or property. public object GetStaticFieldOrProperty(string name) { return this.GetStaticFieldOrProperty(name, BindToEveryThing); } /// /// Sets the static field or property /// /// Name of the field or property /// Value to be set to field or property public void SetStaticFieldOrProperty(string name, object value) { this.SetStaticFieldOrProperty(name, BindToEveryThing, value); } /// /// Gets the static field or property using specified InvokeHelper attributes /// /// Name of the field or property /// Additional invocation attributes /// The static field or property. public object GetStaticFieldOrProperty(string name, BindingFlags bindingFlags) { return this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); } /// /// Sets the static field or property using binding attributes /// /// Name of the field or property /// Additional invocation attributes /// Value to be set to field or property public void SetStaticFieldOrProperty(string name, BindingFlags bindingFlags, object value) { this.InvokeHelperStatic(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags | BindingFlags.Static, new[] { value }, CultureInfo.InvariantCulture); } /// /// Gets the static property /// /// Name of the field or property /// Arguements to the invocation /// The static property. public object GetStaticProperty(string name, params object[] args) { return this.GetStaticProperty(name, BindToEveryThing, args); } /// /// Sets the static property /// /// Name of the property /// Value to be set to field or property /// Arguments to pass to the member to invoke. public void SetStaticProperty(string name, object value, params object[] args) { this.SetStaticProperty(name, BindToEveryThing, value, null, args); } /// /// Sets the static property /// /// Name of the property /// Value to be set to field or property /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// Arguments to pass to the member to invoke. public void SetStaticProperty(string name, object value, Type[] parameterTypes, object[] args) { this.SetStaticProperty(name, BindingFlags.SetProperty, value, parameterTypes, args); } /// /// Gets the static property /// /// Name of the property /// Additional invocation attributes. /// Arguments to pass to the member to invoke. /// The static property. public object GetStaticProperty(string name, BindingFlags bindingFlags, params object[] args) { return this.GetStaticProperty(name, BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, args); } /// /// Gets the static property /// /// Name of the property /// Additional invocation attributes. /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// Arguments to pass to the member to invoke. /// The static property. public object GetStaticProperty(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) { if (parameterTypes != null) { PropertyInfo pi = this.type.GetProperty(name, bindingFlags | BindingFlags.Static, null, null, parameterTypes, null); if (pi == null) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name)); } return pi.GetValue(null, args); } else { return this.InvokeHelperStatic(name, bindingFlags | BindingFlags.GetProperty, args, null); } } /// /// Sets the static property /// /// Name of the property /// Additional invocation attributes. /// Value to be set to field or property /// Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties. public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, params object[] args) { this.SetStaticProperty(name, bindingFlags, value, null, args); } /// /// Sets the static property /// /// Name of the property /// Additional invocation attributes. /// Value to be set to field or property /// An array of objects representing the number, order, and type of the parameters for the indexed property. /// Arguments to pass to the member to invoke. public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, Type[] parameterTypes, object[] args) { if (parameterTypes != null) { PropertyInfo pi = this.type.GetProperty(name, bindingFlags | BindingFlags.Static, null, null, parameterTypes, null); if (pi == null) { throw new ArgumentException( string.Format(CultureInfo.CurrentCulture, "The member specified ({0}) could not be found. You might need to regenerate your private accessor, or the member may be private and defined on a base class. If the latter is true, you need to pass the type that defines the member into PrivateObject's constructor.", name)); } pi.SetValue(null, value, args); } else { object[] pass = new object[(args?.Length ?? 0) + 1]; pass[0] = value; args?.CopyTo(pass, 1); this.InvokeHelperStatic(name, bindingFlags | BindingFlags.SetProperty, pass, null); } } /// /// Invokes the static method /// /// Name of the member /// Additional invocation attributes /// Arguements to the invocation /// Culture /// Result of invocation private object InvokeHelperStatic(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) { try { return this.type.InvokeMember(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, null, args, culture); } catch (TargetInvocationException e) { Debug.Assert(e.InnerException != null, "Inner Exception should not be null."); if (e.InnerException != null) { throw e.InnerException; } throw; } } } /// /// Provides method signature discovery for generic methods. /// internal class RuntimeTypeHelper { /// /// Compares the method signatures of these two methods. /// /// Method1 /// Method2 /// True if they are similiar. internal static bool CompareMethodSigAndName(MethodBase m1, MethodBase m2) { ParameterInfo[] params1 = m1.GetParameters(); ParameterInfo[] params2 = m2.GetParameters(); if (params1.Length != params2.Length) { return false; } int numParams = params1.Length; for (int i = 0; i < numParams; i++) { if (params1[i].ParameterType != params2[i].ParameterType) { return false; } } return true; } /// /// Gets the hierarchy depth from the base type of the provided type. /// /// The type. /// The depth. internal static int GetHierarchyDepth(Type t) { int depth = 0; Type currentType = t; do { depth++; currentType = currentType.BaseType; } while (currentType != null); return depth; } /// /// Finds most dervied type with the provided information. /// /// Candidate matches. /// Number of matches. /// The most derived method. internal static MethodBase FindMostDerivedNewSlotMeth(MethodBase[] match, int cMatches) { int deepestHierarchy = 0; MethodBase methWithDeepestHierarchy = null; for (int i = 0; i < cMatches; i++) { // Calculate the depth of the hierarchy of the declaring type of the // current method. int currentHierarchyDepth = GetHierarchyDepth(match[i].DeclaringType); // Two methods with the same hierarchy depth are not allowed. This would // mean that there are 2 methods with the same name and sig on a given type // which is not allowed, unless one of them is vararg... if (currentHierarchyDepth == deepestHierarchy) { if (methWithDeepestHierarchy != null) { Debug.Assert( methWithDeepestHierarchy != null && ((match[i].CallingConvention & CallingConventions.VarArgs) | (methWithDeepestHierarchy.CallingConvention & CallingConventions.VarArgs)) != 0, "Calling conventions: " + match[i].CallingConvention + " - " + methWithDeepestHierarchy.CallingConvention); } throw new AmbiguousMatchException(); } // Check to see if this method is on the most derived class. if (currentHierarchyDepth > deepestHierarchy) { deepestHierarchy = currentHierarchyDepth; methWithDeepestHierarchy = match[i]; } } return methWithDeepestHierarchy; } /// /// Given a set of methods that match the base criteria, select a method based /// upon an array of types. This method should return null if no method matches /// the criteria. /// /// Binding specification. /// Candidate matches /// Types /// Parameter modifiers. /// Matching method. Null if none matches. internal static MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) { if (match == null) { throw new ArgumentNullException("match"); } int i; int j; Type[] realTypes = new Type[types.Length]; for (i = 0; i < types.Length; i++) { realTypes[i] = types[i].UnderlyingSystemType; } types = realTypes; // If there are no methods to match to, then return null, indicating that no method // matches the criteria if (match.Length == 0) { return null; } // Find all the methods that can be described by the types parameter. // Remove all of them that cannot. int curIdx = 0; for (i = 0; i < match.Length; i++) { ParameterInfo[] par = match[i].GetParameters(); if (par.Length != types.Length) { continue; } for (j = 0; j < types.Length; j++) { Type pCls = par[j].ParameterType; if (pCls.ContainsGenericParameters) { if (pCls.IsArray != types[j].IsArray) { break; } } else { if (pCls == types[j]) { continue; } if (pCls == typeof(object)) { continue; } else { if (!pCls.IsAssignableFrom(types[j])) { break; } } } } if (j == types.Length) { match[curIdx++] = match[i]; } } if (curIdx == 0) { return null; } if (curIdx == 1) { return match[0]; } // Walk all of the methods looking the most specific method to invoke int currentMin = 0; bool ambig = false; int[] paramOrder = new int[types.Length]; for (i = 0; i < types.Length; i++) { paramOrder[i] = i; } for (i = 1; i < curIdx; i++) { int newMin = FindMostSpecificMethod(match[currentMin], paramOrder, null, match[i], paramOrder, null, types, null); if (newMin == 0) { ambig = true; } else { if (newMin == 2) { currentMin = i; ambig = false; currentMin = i; } } } if (ambig) { throw new AmbiguousMatchException(); } return match[currentMin]; } /// /// Finds the most specific method in the two methods provided. /// /// Method 1 /// Parameter order for Method 1 /// Paramter array type. /// Method 2 /// Parameter order for Method 2 /// >Paramter array type. /// Types to search in. /// Args. /// An int representing the match. internal static int FindMostSpecificMethod( MethodBase m1, int[] paramOrder1, Type paramArrayType1, MethodBase m2, int[] paramOrder2, Type paramArrayType2, Type[] types, object[] args) { // Find the most specific method based on the parameters. int res = FindMostSpecific( m1.GetParameters(), paramOrder1, paramArrayType1, m2.GetParameters(), paramOrder2, paramArrayType2, types, args); // If the match was not ambiguous then return the result. if (res != 0) { return res; } // Check to see if the methods have the exact same name and signature. if (CompareMethodSigAndName(m1, m2)) { // Determine the depth of the declaring types for both methods. int hierarchyDepth1 = GetHierarchyDepth(m1.DeclaringType); int hierarchyDepth2 = GetHierarchyDepth(m2.DeclaringType); // The most derived method is the most specific one. if (hierarchyDepth1 == hierarchyDepth2) { return 0; } else if (hierarchyDepth1 < hierarchyDepth2) { return 2; } else { return 1; } } // The match is ambiguous. return 0; } /// /// Finds the most specific method in the two methods provided. /// /// Method 1 /// Parameter order for Method 1 /// Paramter array type. /// Method 2 /// Parameter order for Method 2 /// >Paramter array type. /// Types to search in. /// Args. /// An int representing the match. internal static int FindMostSpecific( ParameterInfo[] p1, int[] paramOrder1, Type paramArrayType1, ParameterInfo[] p2, int[] paramOrder2, Type paramArrayType2, Type[] types, object[] args) { // A method using params is always less specific than one not using params if (paramArrayType1 != null && paramArrayType2 == null) { return 2; } if (paramArrayType2 != null && paramArrayType1 == null) { return 1; } bool p1Less = false; bool p2Less = false; for (int i = 0; i < types.Length; i++) { if (args != null && args[i] == Type.Missing) { continue; } Type c1, c2; // If a param array is present, then either // the user re-ordered the parameters in which case // the argument to the param array is either an array // in which case the params is conceptually ignored and so paramArrayType1 == null // or the argument to the param array is a single element // in which case paramOrder[i] == p1.Length - 1 for that element // or the user did not re-order the parameters in which case // the paramOrder array could contain indexes larger than p.Length - 1 //// so any index >= p.Length - 1 is being put in the param array if (paramArrayType1 != null && paramOrder1[i] >= p1.Length - 1) { c1 = paramArrayType1; } else { c1 = p1[paramOrder1[i]].ParameterType; } if (paramArrayType2 != null && paramOrder2[i] >= p2.Length - 1) { c2 = paramArrayType2; } else { c2 = p2[paramOrder2[i]].ParameterType; } if (c1 == c2) { continue; } if (c1.ContainsGenericParameters || c2.ContainsGenericParameters) { continue; } switch (FindMostSpecificType(c1, c2, types[i])) { case 0: return 0; case 1: p1Less = true; break; case 2: p2Less = true; break; } } // Two way p1Less and p2Less can be equal. All the arguments are the // same they both equal false, otherwise there were things that both // were the most specific type on.... if (p1Less == p2Less) { // it's possible that the 2 methods have same sig and default param in which case we match the one // with the same number of args but only if they were exactly the same (that is p1Less and p2Lees are both false) if (!p1Less && p1.Length != p2.Length && args != null) { if (p1.Length == args.Length) { return 1; } else if (p2.Length == args.Length) { return 2; } } return 0; } else { return (p1Less == true) ? 1 : 2; } } /// /// Finds the most specific type in the two provided. /// /// Type 1 /// Type 2 /// The defining type /// An int representing the match. internal static int FindMostSpecificType(Type c1, Type c2, Type t) { // If the two types are exact move on... if (c1 == c2) { return 0; } if (c1 == t) { return 1; } if (c2 == t) { return 2; } bool c1FromC2; bool c2FromC1; if (c1.IsByRef || c2.IsByRef) { if (c1.IsByRef && c2.IsByRef) { c1 = c1.GetElementType(); c2 = c2.GetElementType(); } else if (c1.IsByRef) { if (c1.GetElementType() == c2) { return 2; } c1 = c1.GetElementType(); } else { if (c2.GetElementType() == c1) { return 1; } c2 = c2.GetElementType(); } } if (c1.IsPrimitive && c2.IsPrimitive) { c1FromC2 = true; c2FromC1 = true; } else { c1FromC2 = c1.IsAssignableFrom(c2); c2FromC1 = c2.IsAssignableFrom(c1); } if (c1FromC2 == c2FromC1) { return 0; } if (c1FromC2) { return 2; } else { return 1; } } } }