// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace OpenTap { /// /// Represents the members of C#/dotnet types. /// public class MemberData : IMemberData { struct MemberName { public string Name { get; set; } public Type DeclaringType { get; set; } } static ConcurrentDictionary dict = new ConcurrentDictionary(); internal static void InvalidateCache() { dict.Clear(); } /// /// Creates a new MemberData for a member of a C#/dotnet type. /// /// /// static public MemberData Create(MemberInfo info) { lock(dict) return dict.GetOrAdd(new MemberName { Name = info.Name, DeclaringType = info.DeclaringType }, x => new MemberData(x.Name, TypeData.FromType(x.DeclaringType))); } /// The System.Reflection.MemberInfo this represents. public readonly MemberInfo Member; private MemberData(string name, TypeData declaringType) : this(declaringType.Type.GetMember(name)[0], declaringType) { } private MemberData(MemberInfo info, TypeData declaringType) { if (info == null) throw new ArgumentNullException(nameof(info)); this.Member = info; this.DeclaringType = declaringType; } IEnumerable attributes = null; /// The attributes of this member. public IEnumerable Attributes => attributes ?? (attributes = Member.GetCustomAttributes()); static Type createDelegateType(MethodInfo method) { var parameters = method.GetParameters().Select(x => x.ParameterType); if (method.ReturnType != typeof(void)) return Expression.GetFuncType(parameters.Append(method.ReturnType).ToArray()); return Expression.GetActionType(parameters.ToArray()); } static Func buildGetter(PropertyInfo propertyInfo) { var instance = Expression.Parameter(typeof(object), "i"); UnaryExpression convert1; if(propertyInfo.DeclaringType.IsValueType) convert1 = Expression.Convert(instance, propertyInfo.DeclaringType); else convert1 = Expression.TypeAs(instance, propertyInfo.DeclaringType); var property = Expression.Property(convert1, propertyInfo); var convert = Expression.TypeAs(property, typeof(object)); var lambda = Expression.Lambda>(convert, instance); var action = lambda.Compile(); return action; } Func propertyGetter = null; /// Gets the value of this member. /// /// public object GetValue(object owner) { switch (Member) { case PropertyInfo Property: if(this.Readable == false) throw new Exception("Cannot get the value of a read-only property."); if (propertyGetter == null) propertyGetter = buildGetter(Property); //Building a lambda expression is an order of magnitude faster than Property.GetValue. return propertyGetter(owner); case FieldInfo Field: return Field.GetValue(owner); case MethodInfo Method: return Delegate.CreateDelegate(createDelegateType(Method), owner, Method, true); default: throw new InvalidOperationException("Unsupported member type: " + Member); } } /// /// Sets the value of this member on an object. /// /// /// public void SetValue(object owner, object value) { switch (Member) { case PropertyInfo Property: Property.SetValue(owner, value); break; case FieldInfo Field: Field.SetValue(owner, value); break; default: throw new InvalidOperationException("Unsupported member type: " + Member); } } /// The name of this member. public string Name => Member.Name; /// /// The declaring type of this member. /// public ITypeData DeclaringType { get; } /// Gets if the member is writable. public bool Writable { get { switch (Member) { case PropertyInfo Property: return Property.CanWrite && Property.GetSetMethod() != null; case FieldInfo Field: return Field.IsInitOnly == false; default: return false; } } } bool? readable; /// Gets if the member is readable. public bool Readable { get { switch (Member) { case PropertyInfo Property: return (readable ?? (readable = Property.CanRead && Property.GetGetMethod() != null)).Value; case FieldInfo _: return true; case MethodInfo _: return true; default: return false; } } } ITypeData typeDescriptor; /// The type descriptor for the object that this member can hold. public ITypeData TypeDescriptor => typeDescriptor ?? (typeDescriptor = getTypeDescriptor()); ITypeData getTypeDescriptor() { switch (Member) { case PropertyInfo Property: return TypeData.FromType(Property.PropertyType); case FieldInfo Field: return TypeData.FromType(Field.FieldType); case MethodInfo Method: return TypeData.FromType(createDelegateType(Method)); default: throw new InvalidOperationException("Unsupported member type: " + Member); } } /// Gets a string representation of this CSharpType. public override string ToString() => $"[{Name}]"; /// Equality for MemberData. Returns true if the other object is a MemberData and refers to same member object. public override bool Equals(object obj) { if (obj is MemberData m && m.Member == Member) return true; return false; } /// GetHash for MemberData. public override int GetHashCode() => Member.GetHashCode() * 32143211 + 88776366; } }