Отражение и автогенерированные типы
У меня есть класс с одним методом, который использует оператор возврата "yield". Вложенный тип создается автоматически. Использование отражения с флагами привязки, установленными в BindingFlags.DeclaredOnly
Я получаю этот вывод:
// Публичные участники из моего класса.
Test.FileSystemObject..ctor
Test.FileSystemObject.GetFiles (каталог DirectoryInfo)
Test.FileSystemObject.GetFiles (String path)// Auto generated nested class. Test.FileSystemObject+<GetFiles>d__4..ctor Test.FileSystemObject+<GetFiles>d__4.<>3__directory Test.FileSystemObject+<GetFiles>d__4.<>4__this Test.FileSystemObject+<GetFiles>d__4.<directories>5__7 Test.FileSystemObject+<GetFiles>d__4.<files>5__8 Test.FileSystemObject+<GetFiles>d__4.<FSO>5__6 Test.FileSystemObject+<GetFiles>d__4.<i>5__9 Test.FileSystemObject+<GetFiles>d__4.<unprocessed>5__5 Test.FileSystemObject+<GetFiles>d__4.directory
Как я могу определить, возвращен ли тип assembly.GetTypes(BindingsFlags)
такое автоматически сгенерированный тип? Я ищу простой способ исключить это.
2 ответа
Вы можете проверить, имеет ли тип [CompilerGenerated]
атрибут:
if (type.GetCustomAttribute(typeof(CompilerGeneratedAttribute), true) != null)
{
...
}
Кроме того, вы можете проверить, содержит ли имя символы, которые не будут действительны в коде пользователя.
Вы можете написать код во время выполнения для компиляции, используя CSharpCodeProvider().CompileAssemblyFromSource()
и зарегистрируйте свой тип в текущем домене сборки. Он будет находиться там до тех пор, пока существует домен. Вызов метода доступа get из результатов автоматически вызывает метод Load из скомпилированной сборки в текущий домен приложения.
Вы также можете использовать Reflection.Emit.TypeBuilder.CreateType()
создать свой тип. Кроме того, вы можете заставить флаг атрибута отображаться здесь как сгенерированный компилятором или другие атрибуты.
var infoConstructor = typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes);
typeBuilder.SetCustomAttribute(infoConstructor, new byte[] { });
Вот пример того, над чем я работаю сегодня для HearthStone Deck Tracker. Его цель - не просто для визуальных целей, а как автоматически сгенерированная коллекция сущностей для всего кода класса, разработанного в обратном порядке. Кажется, это лучше, чем использовать файловый ввод / вывод с T4 и дампами вывода XML. Хотя, это может быть жизнеспособным вариантом, так как X-Doc/oxygen автоматически генерирует HTML-вики-страницы с использованием кода, который можно использовать и скомпилировать в следующей сборке для PDB. Не фанат наворотов, оставайтесь с Reflection.
/// <summary>
/// CreateType
/// </summary>
/// <param name="obj"></param>
/// <param name="name"></param>
/// <param name="properties"></param>
/// <param name="accessor"></param>
/// <param name="hasSubTypes"></param>
/// <returns>The newly created type of the object.</returns>
internal static Type CreateType(this Mirror obj, string name, IEnumerable<string> properties, string accessor = "", bool hasSubTypes = false) {
Type subTypeRef = null;
// Tested Regex @ http://regex101.com
const string subTypes = @"(?:<|(?:\$))([a-zA-Z_]+[0-9`]*)(?:>([a-zA-Z_]+[0-9`]*))";
var match = Regex.Match(name, subTypes);
if (match.Success) {
var refType = match.Groups[1].Value; // Class reference type.
if (match.Groups[2].Success && !string.IsNullOrEmpty(match.Groups[2].Value))
accessor = match.Groups[2].Value; // Class accessor.
// ReSharper disable once TailRecursiveCall
var enumerable = properties as IList<string> ?? properties.ToList();
subTypeRef = CreateType(obj, refType, enumerable, accessor, true);
// Tokenize this for the actual derived class name.
name = name.Substring(0, name.IndexOf('+'));
}
// Check if formating of the class name matches traditional valid syntax.
// Assume at least 3 levels deep.
var toks = name.Split(new[] { '+' }, StringSplitOptions.RemoveEmptyEntries);
Type type = null;
foreach (var tok in toks.Reverse()) {
var o = obj.RefTypes.FirstOrDefault(t => t.Value.Name == tok);
if (!o.Equals(default(KeyValuePair<string, Type>)))
continue;
// Not exists.
var sb = new StringBuilder();
sb.Append(@"
using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Linq;
namespace HearthMirror.TypeBuilder {
[CompilerGenerated]
public class ").Append(tok).AppendLine(@" {");
if (subTypeRef != null)
sb.AppendLine($" public {subTypeRef.Name} {accessor}").AppendLine(" { get; set; }");
sb.Append(" }\n}");
var asm = RuntimeCodeCompiler.CompileCode(sb.ToString());
type = asm.GetType($"{MethodBase.GetCurrentMethod().ReflectedType?.Namespace}.{tok}"); // => generated
// Register our type for reference. This container will handle collisions and throw if encountered.
obj.RefTypes.Add(tok, type);
}
return type;
}
/// <summary>
/// CompileCode
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public static Assembly CompileCode(string code) {
var provider = new CSharpCodeProvider();
var compilerparams = new CompilerParameters { GenerateExecutable = false, GenerateInMemory = true, IncludeDebugInformation = true };
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
try {
var location = assembly.Location;
if (!string.IsNullOrEmpty(location))
compilerparams.ReferencedAssemblies.Add(location);
} catch (NotSupportedException) {
// this happens for dynamic assemblies, so just ignore it.
}
}
var results = provider.CompileAssemblyFromSource(compilerparams, code);
if (results.Errors.HasErrors) {
var errors = new StringBuilder("Compiler Errors :\r\n");
foreach (CompilerError error in results.Errors)
errors.AppendFormat("Line {0},{1}\t: {2}\n", error.Line, error.Column, error.ErrorText);
throw new Exception(errors.ToString());
}
AppDomain.CurrentDomain.Load(results.CompiledAssembly.GetName());
return results.CompiledAssembly;
}