/// <remarks>ISet is not there in .net35, so using HashSet</remarks> private static void ScanProperties <T>(bool aggressiveSearch, List <T> result, object o, int level, HashSet <object> visitedObjects) where T : class { if (o == null) { return; } var type = o.GetType(); try { if (type == null || !type.IsDefined(typeof(NLogConfigurationItemAttribute), true)) { return; } } catch (System.Exception ex) { InternalLogger.Info(ex, "{0}Type reflection not possible for: {1}. Maybe because of .NET Native.", new string(' ', level), o.ToString()); return; } if (visitedObjects.Contains(o)) { return; } visitedObjects.Add(o); if (InternalLogger.IsTraceEnabled) { InternalLogger.Trace("{0}Scanning {1} '{2}'", new string(' ', level), type.Name, o); } if (o is T t) { result.Add(t); if (!aggressiveSearch) { return; } } foreach (PropertyInfo prop in PropertyHelper.GetAllReadableProperties(type)) { if (prop == null || prop.PropertyType == null || prop.PropertyType.IsPrimitive() || prop.PropertyType.IsEnum() || prop.PropertyType == typeof(string)) { continue; } try { if (prop.IsDefined(typeof(NLogConfigurationIgnorePropertyAttribute), true)) { continue; } } catch (System.Exception ex) { InternalLogger.Info(ex, "{0}Type reflection not possible for property {1}. Maybe because of .NET Native.", new string(' ', level + 1), prop.Name); continue; } object value = prop.GetValue(o, null); if (value == null) { continue; } if (InternalLogger.IsTraceEnabled) { InternalLogger.Trace("{0}Scanning Property {1} '{2}' {3}", new string(' ', level + 1), prop.Name, value.ToString(), prop.PropertyType.Namespace); } if (value is IList list) { //try first icollection for syncroot List <object> elements; lock (list.SyncRoot) { elements = new List <object>(list.Count); //no foreach. Even .Cast can lead to Collection was modified after the enumerator was instantiated. for (int i = 0; i < list.Count; i++) { var item = list[i]; elements.Add(item); } } ScanPropertiesList(aggressiveSearch, result, elements, level + 1, visitedObjects); } else { if (value is IEnumerable enumerable) { //new list to prevent: Collection was modified after the enumerator was instantiated. var elements = enumerable as IList <object> ?? enumerable.Cast <object>().ToList(); //note .Cast is tread-unsafe! But at least it isn't a ICollection / IList ScanPropertiesList(aggressiveSearch, result, elements, level + 1, visitedObjects); } else { #if NETSTANDARD if (!prop.PropertyType.IsDefined(typeof(NLogConfigurationItemAttribute), true)) { continue; // .NET native doesn't always allow reflection of System-types (Ex. Encoding) } #endif ScanProperties(aggressiveSearch, result, value, level + 1, visitedObjects); } } } }
private static void ScanProperties <T>(List <T> result, object o, int level, Dictionary <object, int> visitedObjects) where T : class { if (o == null) { return; } if (!o.GetType().IsDefined(typeof(NLogConfigurationItemAttribute), true)) { return; } if (visitedObjects.ContainsKey(o)) { return; } visitedObjects.Add(o, 0); var t = o as T; if (t != null) { result.Add(t); } if (InternalLogger.IsTraceEnabled) { InternalLogger.Trace("{0}Scanning {1} '{2}'", new string(' ', level), o.GetType().Name, o); } foreach (PropertyInfo prop in PropertyHelper.GetAllReadableProperties(o.GetType())) { if (prop.PropertyType.IsPrimitive || prop.PropertyType.IsEnum || prop.PropertyType == typeof(string) || prop.IsDefined(typeof(NLogConfigurationIgnorePropertyAttribute), true)) { continue; } object value = prop.GetValue(o, null); if (value == null) { continue; } var enumerable = value as IEnumerable; if (enumerable != null) { //new list to prevent: Collection was modified after the enumerator was instantiated. var elements = new List <object>(enumerable.Cast <object>()); foreach (object element in elements) { ScanProperties(result, element, level + 1, visitedObjects); } } else { ScanProperties(result, value, level + 1, visitedObjects); } } }