// // Copyright (c) 2019 cactuaroid All Rights Reserved // // // Released under the MIT license // https://github.com/cactuaroid/PrivateObjectExtensions // using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using System.Reflection; namespace System { /// /// Extension methods for PrivateObject /// public static class PrivateObjectExtensions { private static readonly BindingFlags Static = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Static; private static readonly BindingFlags Instance = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance; /// /// Get from private (and any other) field/property. /// If the real type of specified object doesn't contain the specified field/property, /// base types are searched automatically. /// /// The object to get from /// The name of the field/property /// The object got from the field/property /// 'name' is not found. /// Arguments contain null. public static object GetPrivate(this object obj, string name) { if (obj == null) { throw new ArgumentNullException("obj"); } return GetPrivate(obj, name, obj.GetType(), null); } /// /// Get from private (and any other) field/property. /// If the real type of specified object doesn't contain the specified field/property, /// base types are searched automatically. /// /// The type of the field/property /// The object to get from /// The name of the field/property /// The object got from the field/property /// 'name' is not found. /// Arguments contain null. public static T GetPrivate(this object obj, string name) { if (obj == null) { throw new ArgumentNullException("obj"); } return (T)GetPrivate(obj, name, obj.GetType(), typeof(T)); } /// /// Get from private (and any other) field/property with assuming the specified object as specified type. /// If the specified type doesn't contain the specified field/property, /// base types are searched automatically. /// /// The object to get from /// The name of the field/property /// The type of 'obj' for seaching member starting from. Real type of 'obj' is ignored. /// The object got from the field/property /// 'name' is not found. /// 'objType' is not assignable from 'obj'. /// Arguments contain null. public static object GetPrivate(this object obj, string name, Type objType) { return GetPrivate(obj, name, objType, null); } /// /// Get from private (and any other) field/property with assuming the specified object as specified type. /// If the specified type doesn't contain the specified field/property, /// base types are searched automatically. /// /// The type of the field/property /// The object to get from /// The name of the field/property /// The type of 'obj' for seaching member starting from. Real type of 'obj' is ignored. /// The object got from the field/property /// 'name' is not found. /// 'objType' is not assignable from 'obj'. /// Arguments contain null. public static T GetPrivate(this object obj, string name, Type objType) { return (T)GetPrivate(obj, name, objType, typeof(T)); } private static object GetPrivate(object obj, string name, Type objType, Type memberType) { if (obj == null) { throw new ArgumentNullException("obj"); } if (name == null) { throw new ArgumentNullException("name"); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); } if (objType == null) { throw new ArgumentNullException("objType"); } if (!objType.IsAssignableFrom(obj.GetType())) { throw new ArgumentException($"{objType} is not assignable from {obj.GetType()}.", "objType"); } bool memberTypeMatching(Type actualType) => actualType == memberType; if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Instance, out var ownerType)) { return new PrivateObject(obj, new PrivateType(ownerType)).GetFieldOrProperty(name); } else if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Static, out ownerType)) { return new PrivateType(ownerType).GetStaticFieldOrProperty(name); } throw new ArgumentException(((memberType != null) ? memberType + " " : "") + name + " is not found."); } /// /// Get from private (and any other) static field/property. /// /// The type to get from /// The name of the static field/property /// The object got from the static field/property /// 'name' is not found. /// Arguments contain null. public static object GetPrivate(this Type type, string name) { return GetPrivate(type, name, null); } /// /// Get from private (and any other) static field/property. /// /// The type of the field/property /// The type to get from /// The name of the static field/property /// The object got from the static field/property /// 'name' is not found. /// Arguments contain null. public static T GetPrivate(this Type type, string name) { return (T)GetPrivate(type, name, typeof(T)); } private static object GetPrivate(this Type type, string name, Type memberType) { if (type == null) { throw new ArgumentNullException("type"); } if (name == null) { throw new ArgumentNullException("name"); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); } bool memberTypeMatching(Type actualType) => actualType == memberType; if (type.ContainsFieldOrProperty(name, memberType, memberTypeMatching, Static)) { return new PrivateType(type).GetStaticFieldOrProperty(name); } throw new ArgumentException(((memberType != null) ? memberType + " " : "") + name + " is not found."); } /// /// Set to private (and any other) field/property. /// If the real type of specified object doesn't contain the specified field/property, /// base types are searched automatically. /// /// The object to set to /// The name of the field/property /// The value to set for 'name' /// 'name' is not found. /// Arguments contain null. public static void SetPrivate(this object obj, string name, T value) { if (obj == null) { throw new ArgumentNullException("obj"); } SetPrivate(obj, name, value, obj.GetType()); } /// /// Set to private (and any other) field/property with assuming the specified object as specified type. /// If the specified type doesn't contain the specified field/property, /// base types are searched automatically. /// /// The object to set to /// The name of the field/property /// The value to set for 'name' /// The type of 'obj' for seaching member starting from. Real type of 'obj' is ignored. /// 'name' is not found. /// 'objType' is not assignable from 'obj'. /// Arguments contain null. public static void SetPrivate(this object obj, string name, T value, Type objType) { if (obj == null) { throw new ArgumentNullException("obj"); } if (name == null) { throw new ArgumentNullException("name"); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); } if (value == null) { throw new ArgumentNullException("value"); } if (objType == null) { throw new ArgumentNullException("objType"); } if (!objType.IsAssignableFrom(obj.GetType())) { throw new ArgumentException($"{objType} is not assignable from {obj.GetType()}.", "objType"); } if (TrySetPrivate(obj, name, value, objType)) { return; } // retry for the case of getter only property if (TrySetPrivate(obj, GetBackingFieldName(name), value, objType)) { return; } throw new ArgumentException($"{typeof(T)} {name} is not found."); } private static bool TrySetPrivate(object obj, string name, T value, Type objType) { var memberType = typeof(T); bool memberTypeMatching(Type actualType) => actualType.IsAssignableFrom(memberType); try { if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Instance, out var ownerType)) { new PrivateObject(obj, new PrivateType(ownerType)).SetFieldOrProperty(name, value); return true; } else if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Static, out ownerType)) { new PrivateType(ownerType).SetStaticFieldOrProperty(name, value); return true; } } catch (MissingMethodException) { // When getter only property name is given, the property is found but fails to set. return false; } return false; } /// /// Set to private (and any other) static field/property. /// /// The type to set to /// The name of the field/property /// The value to set for 'name' /// 'name' is not found. /// Arguments contain null. public static void SetPrivate(this Type type, string name, T value) { if (type == null) { throw new ArgumentNullException("type"); } if (name == null) { throw new ArgumentNullException("name"); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); } if (TrySetPrivate(type, name, value)) { return; } // retry for the case of getter only property if (TrySetPrivate(type, GetBackingFieldName(name), value)) { return; } throw new ArgumentException($"{typeof(T)} {name} is not found."); } private static bool TrySetPrivate(this Type type, string name, T value) { var memberType = typeof(T); bool memberTypeMatching(Type actualType) => actualType.IsAssignableFrom(memberType); try { if (type.ContainsFieldOrProperty(name, memberType, memberTypeMatching, Static)) { new PrivateType(type).SetStaticFieldOrProperty(name, value); return true; } } catch (MissingMethodException) { // When getter only property name is given, the property is found but fails to set. return false; } return false; } private static string GetBackingFieldName(string propertyName) => $"<{propertyName}>k__BackingField"; // generated backing field name private static bool TryFindFieldOrPropertyOwnerType(Type objType, string name, Type memberType, Func memberTypeMatching, BindingFlags bindingFlag, out Type ownerType) { ownerType = FindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, bindingFlag); return (ownerType != null); } private static Type FindFieldOrPropertyOwnerType(Type objectType, string name, Type memberType, Func memberTypeMatching, BindingFlags bindingFlags) { if (objectType == null) { return null; } if (objectType.ContainsFieldOrProperty(name, memberType, memberTypeMatching, bindingFlags)) { return objectType; } return FindFieldOrPropertyOwnerType(objectType.BaseType, name, memberType, memberTypeMatching, bindingFlags); } private static bool ContainsFieldOrProperty(this Type objectType, string name, Type memberType, Func memberTypeMatching, BindingFlags bindingFlags) { var fields = objectType .GetFields(bindingFlags) .Select((x) => new { Type = x.FieldType, Member = x as MemberInfo }); var properties = objectType .GetProperties(bindingFlags) .Select((x) => new { Type = x.PropertyType, Member = x as MemberInfo }); var members = fields.Concat(properties); return members.Any((actual) => (memberType == null || memberTypeMatching.Invoke(actual.Type)) && actual.Member.Name == name); } } }