|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;
using System.Threading;
using System.Xaml.MS.Impl;
using XAML3 = System.Windows.Markup;
namespace System.Xaml.Schema
{
[Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "This type is internal.")]
class TypeReflector : Reflector
{
private const XamlCollectionKind XamlCollectionKindInvalid = (XamlCollectionKind)byte.MaxValue;
private const BindingFlags AllProperties_BF
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags AttachableProperties_BF
= BindingFlags.Static | BindingFlags.FlattenHierarchy
| BindingFlags.Public | BindingFlags.NonPublic;
private static TypeReflector s_UnknownReflector;
// Thread safety: MemberDictionary implements its own thread-safety.
// Lazy init: These fields are null when uninitialized, and must only be initialized once.
private ThreadSafeDictionary<string, XamlMember> _nonAttachableMemberCache;
private ThreadSafeDictionary<string, XamlMember> _attachableMemberCache;
// Thread safety: never access directly (outside of ctor); always call GetFlag() or SetFlag()
private int _boolTypeBits;
// Thread safety: These dictionaries are thread-safe; always use TryAdd/TryUpdate to write
// Lazy init: These fields are null when uninitialized, and must only be initialized once.
private ThreadSafeDictionary<int, IList<XamlType>> _positionalParameterTypes;
private ConcurrentDictionary<XamlDirective, XamlMember> _aliasedProperties;
// Lazy init: set to XamlCollectionKindInvalid when uninitialized
private XamlCollectionKind _collectionKind;
// Thread safety:
// All fields below should either be either uninitialized or complete;
// never assign intermediate values to them.
// All fields below should be idempotent; assignment race conditions are harmless.
// Lazy init: Check NullableReference.IsSet to determine if these fields have been initialized
private NullableReference<XamlMember> _contentProperty;
private NullableReference<XamlMember> _runtimeNameProperty;
private NullableReference<XamlMember> _xmlLangProperty;
private NullableReference<XamlMember> _dictionaryKeyProperty;
private NullableReference<XamlMember> _uidProperty;
private NullableReference<MethodInfo> _isReadOnlyMethod;
private NullableReference<XamlValueConverter<TypeConverter>> _typeConverter;
private NullableReference<XamlValueConverter<XAML3.ValueSerializer>> _valueSerializer;
private NullableReference<XamlValueConverter<XamlDeferringLoader>> _deferringLoader;
private NullableReference<EventHandler<XAML3.XamlSetMarkupExtensionEventArgs>> _xamlSetMarkupExtensionHandler;
private NullableReference<EventHandler<XAML3.XamlSetTypeConverterEventArgs>> _xamlSetTypeConverterHandler;
private NullableReference<MethodInfo> _addMethod;
private NullableReference<XamlType> _baseType;
private NullableReference<MethodInfo> _getEnumeratorMethod;
// Used for UnknownReflector only
private TypeReflector()
{
_nonAttachableMemberCache = new ThreadSafeDictionary<string, XamlMember>();
_nonAttachableMemberCache.IsComplete = true;
_attachableMemberCache = new ThreadSafeDictionary<string, XamlMember>();
_attachableMemberCache.IsComplete = true;
_baseType.Value = XamlLanguage.Object;
_boolTypeBits = (int)BoolTypeBits.Default | (int)BoolTypeBits.Unknown | (int)BoolTypeBits.WhitespaceSignificantCollection | (int)BoolTypeBits.AllValid;
_collectionKind = XamlCollectionKind.None;
// Set all the nullable references explicitly so that IsSet will be equal to true
_addMethod.Value = null;
_contentProperty.Value = null;
_deferringLoader.Value = null;
_dictionaryKeyProperty.Value = null;
_getEnumeratorMethod.Value = null;
_isReadOnlyMethod.Value = null;
_runtimeNameProperty.Value = null;
_typeConverter.Value = null;
_uidProperty.Value = null;
_valueSerializer.Value = null;
_xamlSetMarkupExtensionHandler.Value = null;
_xamlSetTypeConverterHandler.Value = null;
_xmlLangProperty.Value = null;
CustomAttributeProvider = null;
Invoker = XamlTypeInvoker.UnknownInvoker;
}
public TypeReflector(Type underlyingType)
{
UnderlyingType = underlyingType;
_collectionKind = XamlCollectionKindInvalid;
}
internal static TypeReflector UnknownReflector
{
get
{
if (s_UnknownReflector is null)
{
s_UnknownReflector = new TypeReflector();
}
return s_UnknownReflector;
}
}
#region Static visbility helpers
internal static bool IsVisibleTo(Type type, Assembly accessingAssembly, XamlSchemaContext schemaContext)
{
TypeVisibility visibility = GetVisibility(type);
if (visibility == TypeVisibility.NotVisible)
{
return false;
}
if (visibility == TypeVisibility.Internal &&
!schemaContext.AreInternalsVisibleTo(type.Assembly, accessingAssembly))
{
return false;
}
if (type.IsGenericType)
{
foreach (Type typeArg in type.GetGenericArguments())
{
if (!IsVisibleTo(typeArg, accessingAssembly, schemaContext))
{
return false;
}
}
}
else if (type.HasElementType)
{
return IsVisibleTo(type.GetElementType(), accessingAssembly, schemaContext);
}
return true;
}
internal static bool IsInternal(Type type)
{
return GetVisibility(type) == TypeVisibility.Internal;
}
internal static bool IsPublicOrInternal(MethodBase method)
{
return method.IsPublic || method.IsAssembly || method.IsFamilyOrAssembly;
}
#endregion
#region Data storage
internal IList<XamlType> AllowedContentTypes { get; set; }
internal ThreadSafeDictionary<string, XamlMember> AttachableMembers
{
get
{
if (_attachableMemberCache is null)
{
Interlocked.CompareExchange(ref _attachableMemberCache,
new ThreadSafeDictionary<string, XamlMember>(), null);
}
return _attachableMemberCache;
}
}
internal XamlType BaseType
{
get { return _baseType.Value; }
set { _baseType.Value = value; }
}
internal bool BaseTypeIsSet
{
get { return _baseType.IsSet; }
}
internal XamlCollectionKind CollectionKind
{
get { return _collectionKind; }
set { _collectionKind = value; }
}
internal bool CollectionKindIsSet { get { return _collectionKind != XamlCollectionKindInvalid; } }
internal XamlMember ContentProperty
{
get { return _contentProperty.Value; }
set { _contentProperty.Value = value; }
}
internal bool ContentPropertyIsSet { get { return _contentProperty.IsSet; } }
internal IList<XamlType> ContentWrappers { get; set; }
internal XamlValueConverter<XamlDeferringLoader> DeferringLoader
{
get { return _deferringLoader.Value; }
set { _deferringLoader.Value = value; }
}
internal bool DeferringLoaderIsSet { get { return _deferringLoader.IsSet; } }
internal ICollection<XamlMember> ExcludedReadOnlyMembers { get; set; }
internal XamlType KeyType { get; set; }
internal XamlTypeInvoker Invoker { get; set; }
internal MethodInfo IsReadOnlyMethod
{
get { return _isReadOnlyMethod.Value; }
set { _isReadOnlyMethod.Value = value; }
}
internal bool IsReadOnlyMethodIsSet { get { return _isReadOnlyMethod.IsSet; } }
// No need to check valid flag, this is set in constructor
internal bool IsUnknown { get { return (_boolTypeBits & (int)BoolTypeBits.Unknown) != 0; } }
internal XamlType ItemType { get; set; }
internal XamlType MarkupExtensionReturnType { get; set; }
internal ThreadSafeDictionary<string, XamlMember> Members
{
get
{
if (_nonAttachableMemberCache is null)
{
Interlocked.CompareExchange(ref _nonAttachableMemberCache,
new ThreadSafeDictionary<string, XamlMember>(), null);
}
return _nonAttachableMemberCache;
}
}
internal Dictionary<int, IList<XamlType>> ReflectedPositionalParameters { get; set; }
internal XamlValueConverter<TypeConverter> TypeConverter
{
get { return _typeConverter.Value; }
set { _typeConverter.Value = value; }
}
internal bool TypeConverterIsSet { get { return _typeConverter.IsSet; } }
internal Type UnderlyingType { get; set; }
internal XamlValueConverter<XAML3.ValueSerializer> ValueSerializer
{
get { return _valueSerializer.Value; }
set { _valueSerializer.Value = value; }
}
internal bool ValueSerializerIsSet { get { return _valueSerializer.IsSet; } }
internal EventHandler<XAML3.XamlSetMarkupExtensionEventArgs> XamlSetMarkupExtensionHandler
{
get { return _xamlSetMarkupExtensionHandler.Value; }
set { _xamlSetMarkupExtensionHandler.Value = value; }
}
internal bool XamlSetMarkupExtensionHandlerIsSet { get { return _xamlSetMarkupExtensionHandler.IsSet; } }
internal EventHandler<XAML3.XamlSetTypeConverterEventArgs> XamlSetTypeConverterHandler
{
get { return _xamlSetTypeConverterHandler.Value; }
set { _xamlSetTypeConverterHandler.Value = value; }
}
internal bool XamlSetTypeConverterHandlerIsSet { get { return _xamlSetTypeConverterHandler.IsSet; } }
internal bool TryGetPositionalParameters(int paramCount, out IList<XamlType> result)
{
result = null;
if (_positionalParameterTypes is null)
{
if (IsUnknown)
{
return true;
}
Interlocked.CompareExchange(ref _positionalParameterTypes,
new ThreadSafeDictionary<int, IList<XamlType>>(), null);
}
return _positionalParameterTypes.TryGetValue(paramCount, out result);
}
internal IList<XamlType> TryAddPositionalParameters(int paramCount, IList<XamlType> paramList)
{
Debug.Assert(_positionalParameterTypes is not null, "TryGetPositionalParameters should have been called first");
return _positionalParameterTypes.TryAdd(paramCount, paramList);
}
internal bool TryGetAliasedProperty(XamlDirective directive, out XamlMember member)
{
member = null;
if (IsUnknown)
{
return true;
}
bool result = false;
if (directive == XamlLanguage.Key)
{
result = _dictionaryKeyProperty.IsSet;
member = _dictionaryKeyProperty.Value;
}
else if (directive == XamlLanguage.Name)
{
result = _runtimeNameProperty.IsSet;
member = _runtimeNameProperty.Value;
}
else if (directive == XamlLanguage.Uid)
{
result = _uidProperty.IsSet;
member = _uidProperty.Value;
}
else if (directive == XamlLanguage.Lang)
{
result = _xmlLangProperty.IsSet;
member = _xmlLangProperty.Value;
}
else if (_aliasedProperties is not null)
{
result = _aliasedProperties.TryGetValue(directive, out member);
}
return result;
}
internal void TryAddAliasedProperty(XamlDirective directive, XamlMember member)
{
Debug.Assert(!IsUnknown);
if (directive == XamlLanguage.Key)
{
_dictionaryKeyProperty.Value = member;
}
else if (directive == XamlLanguage.Name)
{
_runtimeNameProperty.Value = member;
}
else if (directive == XamlLanguage.Uid)
{
_uidProperty.Value = member;
}
else if (directive == XamlLanguage.Lang)
{
_xmlLangProperty.Value = member;
}
else
{
if (_aliasedProperties is null)
{
var dict = XamlSchemaContext.CreateDictionary<XamlDirective, XamlMember>();
Interlocked.CompareExchange(ref _aliasedProperties, dict, null);
}
_aliasedProperties.TryAdd(directive, member);
}
}
internal MethodInfo AddMethod
{
get { return _addMethod.Value; }
set { _addMethod.Value = value; }
}
internal bool AddMethodIsSet { get { return _addMethod.IsSet; } }
internal MethodInfo GetEnumeratorMethod
{
get { return _getEnumeratorMethod.Value; }
set { _getEnumeratorMethod.Value = value; }
}
internal bool GetEnumeratorMethodIsSet { get { return _getEnumeratorMethod.IsSet; } }
#endregion
// We don't cache NameScopeProperty because it's only used at the root.
// But we have the lookup logic here so that we don't need to do reflection in ObjectWriter.
internal static XamlMember LookupNameScopeProperty(XamlType xamlType)
{
if (xamlType.UnderlyingType is null)
{
return null;
}
// We only check this once, at the root of the doc, and only in ObjectWriter.
// So it's fine to use live reflection here.
object obj = GetCustomAttribute(typeof(XAML3.NameScopePropertyAttribute), xamlType.UnderlyingType);
XAML3.NameScopePropertyAttribute nspAttr = obj as XAML3.NameScopePropertyAttribute;
if (nspAttr is not null)
{
Type ownerType = nspAttr.Type;
string propertyName = nspAttr.Name;
XamlMember prop;
if (ownerType is not null)
{
XamlType ownerXamlType = xamlType.SchemaContext.GetXamlType(ownerType);
prop = ownerXamlType.GetAttachableMember(propertyName);
}
else
{
prop = xamlType.GetMember(propertyName);
}
return prop;
}
return null;
}
#region Member lookup
internal PropertyInfo LookupProperty(string name)
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
PropertyInfo pi = GetNonIndexerProperty(name);
if (pi is not null && IsPrivate(pi))
{
pi = null;
}
return pi;
}
internal EventInfo LookupEvent(string name)
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
// In case of shadowing, Type.GetEvent returns the most derived Event
EventInfo ei = UnderlyingType.GetEvent(name, AllProperties_BF);
if (ei is not null && IsPrivate(ei))
{
ei = null;
}
return ei;
}
internal void LookupAllMembers(out ICollection<PropertyInfo> newProperties,
out ICollection<EventInfo> newEvents, out List<XamlMember> knownMembers)
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
Debug.Assert(_nonAttachableMemberCache is not null, "Members property should have been invoked before this");
PropertyInfo[] propList = UnderlyingType.GetProperties(AllProperties_BF);
EventInfo[] eventList = UnderlyingType.GetEvents(AllProperties_BF);
knownMembers = new List<XamlMember>(propList.Length + eventList.Length);
newProperties = FilterProperties(propList, knownMembers, true);
newEvents = FilterEvents(eventList, knownMembers);
}
// Returns properties that don't yet have corresponding XamlMembers
internal IList<PropertyInfo> LookupRemainingProperties()
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
Debug.Assert(_nonAttachableMemberCache is not null, "Members property should have been invoked before this");
PropertyInfo[] propList = UnderlyingType.GetProperties(AllProperties_BF);
return FilterProperties(propList, null, false);
}
private IList<PropertyInfo> FilterProperties(PropertyInfo[] propList, List<XamlMember> knownMembers, bool skipKnownNegatives)
{
Dictionary<string, PropertyInfo> result = new Dictionary<string, PropertyInfo>(propList.Length);
for (int i = 0; i < propList.Length; i++)
{
PropertyInfo currentProp = propList[i];
if (currentProp.GetIndexParameters().Length > 0)
{
continue;
}
XamlMember knownMember;
if (_nonAttachableMemberCache.TryGetValue(currentProp.Name, out knownMember))
{
if (knownMember is not null)
{
if (knownMembers is not null)
{
knownMembers.Add(knownMember);
}
continue;
}
else if (skipKnownNegatives)
{
continue;
}
}
PropertyInfo shadowedProp;
if (result.TryGetValue(currentProp.Name, out shadowedProp))
{
if (shadowedProp.DeclaringType.IsAssignableFrom(currentProp.DeclaringType))
{
// replace less-derived with more-derived prop
result[currentProp.Name] = currentProp;
}
// else currentProp is the less-derived one; ignore it
}
else
{
result.Add(currentProp.Name, currentProp);
}
}
// Remove private properties
// Note: this needs to be done after we've walked the entire property list, because
// a private shadowing property in a derived class should still hide the base property
if (result.Count == 0)
{
return null;
}
List<PropertyInfo> filteredResult = new List<PropertyInfo>(result.Count);
foreach (PropertyInfo property in result.Values)
{
if (!IsPrivate(property))
{
filteredResult.Add(property);
}
}
return filteredResult;
}
private ICollection<EventInfo> FilterEvents(EventInfo[] eventList, List<XamlMember> knownMembers)
{
Dictionary<string, EventInfo> result = new Dictionary<string, EventInfo>(eventList.Length);
for (int i = 0; i < eventList.Length; i++)
{
EventInfo currentEvent = eventList[i];
XamlMember knownMember;
if (_nonAttachableMemberCache.TryGetValue(currentEvent.Name, out knownMember))
{
if (knownMember is not null)
{
knownMembers.Add(knownMember);
}
continue;
}
EventInfo shadowedEvent;
if (result.TryGetValue(currentEvent.Name, out shadowedEvent))
{
if (shadowedEvent.DeclaringType.IsAssignableFrom(currentEvent.DeclaringType))
{
// replace less-derived with more-derived event
result[currentEvent.Name] = currentEvent;
}
// else currentEvent is the less-derived one; ignore it
}
else
{
result.Add(currentEvent.Name, currentEvent);
}
}
// Remove private events
// Note: this needs to be done after we've walked the entire event list, because
// a private shadowing event in a derived class should still hide the base event
if (result.Count == 0)
{
return null;
}
List<EventInfo> filteredResult = new List<EventInfo>(result.Count);
foreach (EventInfo evt in result.Values)
{
if (!IsPrivate(evt))
{
filteredResult.Add(evt);
}
}
return filteredResult;
}
private PropertyInfo GetNonIndexerProperty(string name)
{
// Choose the most derived non-index property, in case of shadowing
PropertyInfo mostDerived = null;
MemberInfo[] infos = UnderlyingType.GetMember(name, MemberTypes.Property, AllProperties_BF);
foreach (PropertyInfo pi in infos)
{
if (pi.GetIndexParameters().Length == 0)
{
if (mostDerived is null || mostDerived.DeclaringType.IsAssignableFrom(pi.DeclaringType))
{
mostDerived = pi;
}
}
}
return mostDerived;
}
private static bool IsPrivate(PropertyInfo pi)
{
return IsPrivateOrNull(pi.GetGetMethod(true)) &&
IsPrivateOrNull(pi.GetSetMethod(true));
}
private static bool IsPrivate(EventInfo ei)
{
return IsPrivateOrNull(ei.GetAddMethod(true));
}
private static bool IsPrivateOrNull(MethodInfo mi)
{
return mi is null || mi.IsPrivate;
}
#endregion
#region Attachable member lookup
private void PickAttachablePropertyAccessors(List<MethodInfo> getters,
List<MethodInfo> setters, out MethodInfo getter, out MethodInfo setter)
{
List<KeyValuePair<MethodInfo, MethodInfo>> candidates =
new List<KeyValuePair<MethodInfo, MethodInfo>>();
if (setters is not null && getters is not null)
{
foreach (MethodInfo curSetter in setters)
{
foreach (MethodInfo curGetter in getters)
{
ParameterInfo[] getterParams = curGetter.GetParameters();
ParameterInfo[] setterParams = curSetter.GetParameters();
if (getterParams[0].ParameterType == setterParams[0].ParameterType &&
curGetter.ReturnType == setterParams[1].ParameterType)
{
candidates.Add(new KeyValuePair<MethodInfo, MethodInfo>(curGetter, curSetter));
}
}
}
}
// There perhaps should be code here to collect all the properties
// with the given name, and their respective declaring types, and
// check "IsAssignableFrom" to find the most derived type/property, etc.
// OR ... we can use the undocumented fact that the most derived
// type/properties appear to be returned first.
// This is for cases where there are multiple overloaded get/set pairs
// for the same attached property name (or multiple overloaded setters with no getter, or multiple adders for an event).
if (candidates.Count > 0)
{
getter = candidates[0].Key;
setter = candidates[0].Value;
}
else if (setters is null || setters.Count == 0
|| (getters is not null && getters.Count > 0 && UnderlyingType.IsVisible && getters[0].IsPublic && !setters[0].IsPublic))
{
getter = getters[0];
setter = null;
}
else
{
getter = null;
setter = setters[0];
}
}
private MethodInfo PickAttachableEventAdder(IEnumerable<MethodInfo> adders)
{
if (adders is not null)
{
// See disambiguation note in PickAttachablePropertyAccessors
foreach (MethodInfo adder in adders)
{
if (!adder.IsPrivate)
{
return adder;
}
}
}
return null;
}
internal bool LookupAttachableProperty(string name, out MethodInfo getter, out MethodInfo setter)
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
List<MethodInfo> setters = LookupStaticSetters(name);
List<MethodInfo> getters = LookupStaticGetters(name);
if ((setters is null || setters.Count == 0) && (getters is null || getters.Count == 0))
{
getter = null;
setter = null;
return false;
}
PickAttachablePropertyAccessors(getters, setters, out getter, out setter);
return true;
}
internal MethodInfo LookupAttachableEvent(string name)
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
List<MethodInfo> adders = LookupStaticAdders(name);
if (adders is null || adders.Count == 0)
{
return null;
}
return PickAttachableEventAdder(adders);
}
private void LookupAllStaticAccessors(out Dictionary<string, List<MethodInfo>> getters,
out Dictionary<string, List<MethodInfo>> setters, out Dictionary<string, List<MethodInfo>> adders)
{
getters = new Dictionary<string,List<MethodInfo>>();
setters = new Dictionary<string,List<MethodInfo>>();
adders = new Dictionary<string,List<MethodInfo>>();
MethodInfo[] allMethods = UnderlyingType.GetMethods(AttachableProperties_BF);
if (UnderlyingType.IsVisible)
{
LookupAllStaticAccessorsHelper(allMethods, getters, setters, adders, true);
}
else
{
LookupAllStaticAccessorsHelper(allMethods, getters, setters, adders, false);
}
}
private void LookupAllStaticAccessorsHelper(MethodInfo[] allMethods, Dictionary<string,List<MethodInfo>> getters,
Dictionary<string, List<MethodInfo>> setters, Dictionary<string, List<MethodInfo>> adders, bool isUnderlyingTypePublic)
{
foreach (MethodInfo method in allMethods)
{
if (!method.IsPrivate)
{
string name;
if (IsAttachablePropertyGetter(method, out name))
{
AddToMultiDict(getters, name, method, isUnderlyingTypePublic);
}
else if (IsAttachablePropertySetter(method, out name))
{
AddToMultiDict(setters, name, method, isUnderlyingTypePublic);
}
else if (IsAttachableEventAdder(method, out name))
{
AddToMultiDict(adders, name, method, isUnderlyingTypePublic);
}
}
}
}
private List<MethodInfo> LookupStaticAdders(string name)
{
string adderName = KnownStrings.Add + name + KnownStrings.Handler;
MemberInfo[] adders = UnderlyingType.GetMember(adderName, MemberTypes.Method, AttachableProperties_BF);
List<MethodInfo> preferredAdders, otherAdders;
PrioritizeAccessors(adders, true /*isEvent*/, false /*isGetter*/, out preferredAdders, out otherAdders);
return preferredAdders ?? otherAdders;
}
private List<MethodInfo> LookupStaticGetters(string name)
{
MemberInfo[] getters = UnderlyingType.GetMember(KnownStrings.Get + name, MemberTypes.Method, AttachableProperties_BF);
List<MethodInfo> preferredGetters, otherGetters;
PrioritizeAccessors(getters, false /*isEvent*/, true /*isGetter*/, out preferredGetters, out otherGetters);
return preferredGetters ?? otherGetters;
}
private List<MethodInfo> LookupStaticSetters(string name)
{
MemberInfo[] setters = UnderlyingType.GetMember(KnownStrings.Set + name, MemberTypes.Method, AttachableProperties_BF);
List<MethodInfo> preferredSetters, otherSetters;
PrioritizeAccessors(setters, false /*isEvent*/, false /*isGetter*/, out preferredSetters, out otherSetters);
return preferredSetters ?? otherSetters;
}
// this method prioritizes attachable property getters/setters when the underlying type is public
// To conform with 3.0 behavior, public getter/setter is preferred even when it does not have a matching setter/getter
private void PrioritizeAccessors(MemberInfo[] accessors, bool isEvent, bool isGetter, out List<MethodInfo> preferredAccessors, out List<MethodInfo> otherAccessors)
{
preferredAccessors = null;
otherAccessors = null;
if (UnderlyingType.IsVisible)
{
foreach (MethodInfo accessor in accessors)
{
if (accessor.IsPublic && IsAttachablePropertyAccessor(isEvent, isGetter, accessor))
{
if (preferredAccessors is null)
{
preferredAccessors = new List<MethodInfo>();
}
preferredAccessors.Add(accessor);
}
else if (!accessor.IsPrivate && IsAttachablePropertyAccessor(isEvent, isGetter, accessor))
{
if (otherAccessors is null)
{
otherAccessors = new List<MethodInfo>();
}
otherAccessors.Add(accessor);
}
}
}
else
{
foreach (MethodInfo accessor in accessors)
{
if (!accessor.IsPrivate && IsAttachablePropertyAccessor(isEvent, isGetter, accessor))
{
if (preferredAccessors is null)
{
preferredAccessors = new List<MethodInfo>();
}
preferredAccessors.Add(accessor);
}
}
}
}
private bool IsAttachablePropertyAccessor(bool isEvent, bool isGetter, MethodInfo accessor)
{
if (isEvent)
{
return IsAttachableEventAdder(accessor);
}
if (isGetter)
{
return IsAttachablePropertyGetter(accessor);
}
return IsAttachablePropertySetter(accessor);
}
private static void AddToMultiDict(Dictionary<string, List<MethodInfo>> dict, string name, MethodInfo value, bool isUnderlyingTypePublic)
{
List<MethodInfo> list;
if (dict.TryGetValue(name, out list))
{
// To conform with how 3.0 behaved,
// if the underlying type is public, public accessors are preferred over non-publics;
// if the underlying type is non-public, we have no preferences
if (isUnderlyingTypePublic)
{
if (value.IsPublic)
{
if (!list[0].IsPublic)
{
// if the list contains non-public accessor,
// get rid of all of them, because we found a public accessor
// which we are adding to the list
list.Clear();
}
list.Add(value);
}
else
{
if (!list[0].IsPublic)
{
list.Add(value);
}
}
}
else
{
list.Add(value);
}
}
else
{
list = new List<MethodInfo>();
dict.Add(name, list);
list.Add(value);
}
}
private bool IsAttachablePropertyGetter(MethodInfo mi, out string name)
{
name = null;
if (!KS.StartsWith(mi.Name, KnownStrings.Get))
{
return false;
}
if (!IsAttachablePropertyGetter(mi))
{
return false;
}
name = mi.Name.Substring(KnownStrings.Get.Length);
return true;
}
private bool IsAttachablePropertyGetter(MethodInfo mi)
{
// Static Getter has one argument and does not return void
ParameterInfo[] pmi = mi.GetParameters();
return (pmi.Length == 1) && (mi.ReturnType != typeof(void));
}
private bool IsAttachablePropertySetter(MethodInfo mi, out string name)
{
name = null;
if (!KS.StartsWith(mi.Name, KnownStrings.Set))
{
return false;
}
if (!IsAttachablePropertySetter(mi))
{
return false;
}
name = mi.Name.Substring(KnownStrings.Set.Length);
return true;
}
private bool IsAttachablePropertySetter(MethodInfo mi)
{
// Static Setter has two arguments
ParameterInfo[] pmi = mi.GetParameters();
return (pmi.Length == 2);
}
private bool IsAttachableEventAdder(MethodInfo mi, out string name)
{
name = null;
if (!KS.StartsWith(mi.Name, KnownStrings.Add) || !KS.EndsWith(mi.Name, KnownStrings.Handler))
{
return false;
}
if (!IsAttachableEventAdder(mi))
{
return false;
}
name = mi.Name.Substring(KnownStrings.Add.Length,
mi.Name.Length - KnownStrings.Add.Length - KnownStrings.Handler.Length);
return true;
}
private bool IsAttachableEventAdder(MethodInfo mi)
{
// Static Adder has two arguments, and second is a delegate
ParameterInfo[] pmi = mi.GetParameters();
return (pmi.Length == 2) && typeof(Delegate).IsAssignableFrom(pmi[1].ParameterType);
}
// Unlike other TypeReflector methods, this one interacts direclty with SchemaContext.
// That is the cleanest way to pass back the information we need without JITting or boxing.
internal IList<XamlMember> LookupAllAttachableMembers(XamlSchemaContext schemaContext)
{
Debug.Assert(UnderlyingType is not null, "Caller should check for UnderlyingType == null");
Debug.Assert(_attachableMemberCache is not null, "AttachableMembers property should have been invoked before this");
List<XamlMember> result = new List<XamlMember>();
Dictionary<string, List<MethodInfo>> getters;
Dictionary<string, List<MethodInfo>> setters;
Dictionary<string, List<MethodInfo>> adders;
LookupAllStaticAccessors(out getters, out setters, out adders);
GetOrCreateAttachableProperties(schemaContext, result, getters, setters);
GetOrCreateAttachableEvents(schemaContext, result, adders);
return result;
}
private void GetOrCreateAttachableProperties(XamlSchemaContext schemaContext, List<XamlMember> result,
Dictionary<string, List<MethodInfo>> getters, Dictionary<string, List<MethodInfo>> setters)
{
foreach (KeyValuePair<string, List<MethodInfo>> nameAndSetterList in setters)
{
string name = nameAndSetterList.Key;
XamlMember member = null;
if (!_attachableMemberCache.TryGetValue(name, out member))
{
List<MethodInfo> getterList;
getters.TryGetValue(name, out getterList);
// removing the current entry from getters dictionary because it is not needed anymore
getters.Remove(name);
MethodInfo getter, setter;
PickAttachablePropertyAccessors(getterList, nameAndSetterList.Value, out getter, out setter);
member = schemaContext.GetAttachableProperty(name, getter, setter);
// Filter out read-only properties except for dictionaries and collections
if (member.IsReadOnly && !member.Type.IsUsableAsReadOnly)
{
member = null;
}
}
if (member is not null)
{
result.Add(member);
}
}
foreach (KeyValuePair<string, List<MethodInfo>> nameAndGetterList in getters)
{
string name = nameAndGetterList.Key;
XamlMember member = null;
if (!_attachableMemberCache.TryGetValue(name, out member))
{
member = schemaContext.GetAttachableProperty(name, nameAndGetterList.Value[0], null);
}
result.Add(member);
}
}
private void GetOrCreateAttachableEvents(XamlSchemaContext schemaContext,
List<XamlMember> result, Dictionary<string, List<MethodInfo>> adders)
{
foreach (KeyValuePair<string, List<MethodInfo>> nameAndAdderList in adders)
{
string name = nameAndAdderList.Key;
XamlMember member = null;
if (!_attachableMemberCache.TryGetValue(name, out member))
{
MethodInfo adder = PickAttachableEventAdder(nameAndAdderList.Value);
member = schemaContext.GetAttachableEvent(name, adder);
}
if (member is not null)
{
result.Add(member);
}
}
}
#endregion
#region Flag Management
internal bool? GetFlag(BoolTypeBits typeBit)
{
return GetFlag(_boolTypeBits, (int)typeBit);
}
internal void SetFlag(BoolTypeBits typeBit, bool value)
{
SetFlag(ref _boolTypeBits, (int)typeBit, value);
}
#endregion
// Used by Reflector for attribute lookups
protected override MemberInfo Member
{
get { return UnderlyingType; }
}
private static object GetCustomAttribute(Type attrType, Type reflectedType)
{
object[] objs = reflectedType.GetCustomAttributes(attrType, true);
if (objs.Length == 0)
{
return null;
}
if (objs.Length > 1)
{
string message = SR.Format(SR.TooManyAttributesOnType,
reflectedType.Name, attrType.Name);
throw new XamlSchemaException(message);
}
return objs[0];
}
private static TypeVisibility GetVisibility(Type type)
{
bool nestedTypeIsInternal = false;
// If the type is nested, we need to check its entire declaring hierarchy
while (type.IsNested)
{
if (type.IsNestedAssembly || type.IsNestedFamORAssem)
{
nestedTypeIsInternal = true;
}
else if (!type.IsNestedPublic)
{
// Not public or internal
return TypeVisibility.NotVisible;
}
type = type.DeclaringType;
}
bool outerTypeIsInternal = type.IsNotPublic;
return (outerTypeIsInternal || nestedTypeIsInternal) ? TypeVisibility.Internal : TypeVisibility.Public;
}
private enum TypeVisibility
{
NotVisible,
Internal,
Public
}
internal class ThreadSafeDictionary<K,V> : Dictionary<K, V> where V : class
{
bool _isComplete;
internal ThreadSafeDictionary()
{
}
public bool IsComplete
{
get { return _isComplete; }
set
{
Debug.Assert(value);
// This instance is always held in a private field, safe to lock on
lock (this)
{
SetComplete();
}
}
}
public new bool TryGetValue(K name, out V member)
{
// This instance is always held in a private field, safe to lock on
lock (this)
{
return base.TryGetValue(name, out member);
}
}
public new V TryAdd(K name, V member)
{
// This instance is always held in a private field, safe to lock on
lock (this)
{
V result;
if (!base.TryGetValue(name, out result))
{
if (!IsComplete)
{
Add(name, member);
}
result = member;
}
return result;
}
}
private void SetComplete()
{
// Delete any saved null so that they don't get returned to the user.
// They're superfluous now that we know the list is complete.
List<K> listOfNulls = null;
foreach (KeyValuePair<K, V> pair in this)
{
if (pair.Value is null)
{
if (listOfNulls is null)
{
listOfNulls = new List<K>();
}
listOfNulls.Add(pair.Key);
}
}
if (listOfNulls is not null)
{
for (int i = 0; i < listOfNulls.Count; i++)
{
Remove(listOfNulls[i]);
}
}
_isComplete = true;
}
}
}
}
|