Сборки .NET: понимание видимости типов - PullRequest
3 голосов
/ 08 августа 2010

Я пытаюсь воспроизвести что-то, что System.Xml.Serialization уже делает, но для другого источника данных.На данный момент задача ограничивается только десериализацией.Т.е. дан определенный источник данных, которые я умею читать.Напишите библиотеку, которая принимает случайный тип, изучает ее поля / свойства посредством отражения, затем генерирует и компилирует класс «reader», который может принимать источник данных и экземпляр этого случайного типа, и записывает данные из источника данных в поля / свойства объекта.

вот упрощенная выдержка из моего класса ReflectionHelper

public class ReflectionHelper
{
    public abstract class FieldReader<T> 
    {
        public abstract void Fill(T entity, XDataReader reader);
    }

    public static FieldReader<T> GetFieldReader<T>()
    {
        Type t = typeof(T);
        string className = GetCSharpName(t);
        string readerClassName = Regex.Replace(className, @"\W+", "_") + "_FieldReader";
        string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);

        CompilerParameters prms = new CompilerParameters();
        prms.GenerateInMemory = true;
        prms.ReferencedAssemblies.Add("System.Data.dll");
        prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
        prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);

        CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] {source});

        if (compiled.Errors.Count > 0)
        {
            StringWriter w = new StringWriter();
            w.WriteLine("Error(s) compiling {0}:", readerClassName);
            foreach (CompilerError e in compiled.Errors)
                w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
            w.WriteLine();
            w.WriteLine("Generated code:");
            w.WriteLine(source);
            throw new Exception(w.GetStringBuilder().ToString());
        }

        return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
    }

    private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<EntityField> fields)
    {
        StringWriter w = new StringWriter();

        // write out field setters here

        return @"
using System;
using System.Data;

namespace " + ns + @".Generated
{
    public class " + readerClassName + @" : ReflectionHelper.FieldReader<" + className + @">
    {
        public void Fill(" + className + @" e, XDataReader reader)
        {
" + w.GetStringBuilder().ToString() + @"
        }
    }
}
";
    }
}

и вызывающего кода:

class Program
{
    static void Main(string[] args)
    {
        ReflectionHelper.GetFieldReader<Foo>();
        Console.ReadKey(true);
    }

    private class Foo
    {
        public string Field1 = null;
        public int? Field2 = null;
    }
}

Динамическая компиляция курса завершается неудачно, поскольку класс Foo не виденвне класса программы.Но!XML-десериализатор .NET каким-то образом работает вокруг этого - и вопрос: как?После часа копания System.Xml.Serialization через Reflector я пришел к выводу, что мне не хватает каких-то базовых знаний здесь, и я не совсем уверен, что я ищу ...

Также вполне возможно, что яя изобретаю колесо и / или копаю в неправильном направлении, в этом случае, пожалуйста, говорите!

Ответы [ 3 ]

1 голос
/ 11 августа 2010

Вам не нужно создавать динамическую сборку и динамически компилировать код для десериализации объекта. XmlSerializer тоже этого не делает - он использует API Reflection, в частности он использует следующие простые понятия:

Получение набора полей из любого типа

Reflection предоставляет метод GetFields() для этой цели:

foreach (var field in myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    // ...

Я включил здесь параметр BindingFlags, чтобы он включал непубличные поля, потому что в противном случае он будет возвращать только открытые по умолчанию.

Установка значения поля в любом типе

Reflection предоставляет функцию SetValue() для этой цели. Вы вызываете это для экземпляра FieldInfo (который возвращается из GetFields() выше) и даете ему экземпляр, в котором вы хотите изменить значение этого поля, и значение для его установки:

field.SetValue(myObject, myValue);

Это в основном эквивалентно myObject.Field = myValue;, за исключением того, что поле идентифицируется во время выполнения, а не во время компиляции.

Собираем все вместе

Вот простой пример. Обратите внимание, что вам нужно расширить это, чтобы работать с более сложными типами, такими как массивы, например.

public static T Deserialize<T>(XDataReader dataReader) where T : new()
{
    return (T) deserialize(typeof(T), dataReader);
}
private static object deserialize(Type t, XDataReader dataReader)
{
    // Handle the basic, built-in types
    if (t == typeof(string))
        return dataReader.ReadString();
    // etc. for int and all the basic types

    // Looks like the type t is not built-in, so assume it’s a class.
    // Create an instance of the class
    object result = Activator.CreateInstance(t);

    // Iterate through the fields and recursively deserialize each
    foreach (var field in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        field.SetValue(result, deserialize(field.FieldType, dataReader));

    return result;
}

Заметьте, я должен был сделать некоторые предположения относительно XDataReader, в частности, что он может просто прочитать строку, подобную этой. Я уверен, что вы сможете изменить его, чтобы он работал с вашим конкретным классом читателей.

Как только вы расширили это для поддержки всех необходимых вам типов (включая int? в вашем примере класса), вы можете десериализовать объект, вызвав:

Foo myFoo = Deserialize<Foo>(myDataReader);

и вы можете сделать это, даже если Foo является закрытым типом, как в вашем примере.

0 голосов
/ 10 августа 2010

Если я пытаюсь использовать sgen.exe (автономный компилятор сборки XML-сериализации), я получаю следующее сообщение об ошибке:

Warning: Ignoring 'TestApp.Program'.
  - TestApp.Program is inaccessible due to its protection level. Only public types can be processed.
Warning: Ignoring 'TestApp.Program+Foo'.
  - TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
Assembly 'c:\...\TestApp\bin\debug\TestApp.exe' does not contain any types that can be serialized using XmlSerializer.

Вызов new XmlSerializer(typeof(Foo)) в вашем примере кода приводит к:

System.InvalidOperationException: TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.

Так с чего вы взяли, что XmlSerializer может справиться с этим?

Однако помните, что во время выполнения таких ограничений нет. Доверенный код, использующий отражение, может игнорировать модификаторы доступа. Это то, что делает двоичная сериализация .NET.

Например, если вы генерируете код IL во время выполнения, используя DynamicMethod , тогда вы можете передать skipVisibility = true, чтобы избежать любых проверок видимости полей / классов.

0 голосов
/ 10 августа 2010

Я немного работал над этим.Я не уверен, поможет ли это, но, во всяком случае, я думаю, что это может быть так.Недавно я работал с сериализацией и десериализацией класса, который мне нужно было отправить по сети.Поскольку были две разные программы (клиент и сервер), сначала я реализовал класс в обоих источниках, а затем использовал сериализацию.Это не удалось, так как .Net сказал мне, что у него не тот же идентификатор (я не уверен, но это был какой-то идентификатор сборки).

Ну, немного погуглив, я обнаружил, что это потому, что сериализованныйкласс находился в разных сборках, поэтому было решено поместить этот класс в независимую библиотеку, а затем скомпилировать клиент и сервер с этой библиотекой.Я использовал ту же идею с вашим кодом, поэтому я поместил класс Foo и класс FieldReader в независимую библиотеку, скажем:

namespace FooLibrary
{    
    public class Foo
    {
        public string Field1 = null;
        public int? Field2 = null;
    }

    public abstract class FieldReader<T>
    {
        public abstract void Fill(T entity, IDataReader reader);
    }    
}

скомпилируйте его и добавьте в другой источник (using FooLibrary;)

это код, который я использовал.Это не совсем то же самое, что вашдля объекта

public class ReflectionHelper
{
    public static FieldReader<T> GetFieldReader<T>()
    {
        Type t = typeof(T);
        string className = t.Name;
        string readerClassName = Regex.Replace(className, @"\W+", "_") + "_FieldReader";
        object[] fields = new object[10];
        string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);

        CompilerParameters prms = new CompilerParameters();
        prms.GenerateInMemory = true;
        prms.ReferencedAssemblies.Add("System.Data.dll");
        prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
        prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
        prms.ReferencedAssemblies.Add("FooLibrary1.dll");

        CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] { source });

        if (compiled.Errors.Count > 0)
        {
            StringWriter w = new StringWriter();
            w.WriteLine("Error(s) compiling {0}:", readerClassName);
            foreach (CompilerError e in compiled.Errors)
                w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
            w.WriteLine();
            w.WriteLine("Generated code:");
            w.WriteLine(source);
            throw new Exception(w.GetStringBuilder().ToString());
        }

        return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
    }

    private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<object> fields)
    {
        StringWriter w = new StringWriter();

        // write out field setters here

        return @"   
using System;   
using System.Data;   
namespace " + ns + ".Generated   
{    
   public class " + readerClassName + @" : FieldReader<" + className + @">    
   {        
         public override void Fill(" + className + @" e, IDataReader reader)          
         " + w.GetStringBuilder().ToString() +         
   }    
  }";        
 } 
}

, кстати, я обнаружил крошечную ошибку, вы должны использовать new или override с методом Fill, поскольку он является абстрактным.

Хорошо, я должен признать, что GetFieldReaderвозвращает ноль, но по крайней мере компилятор его компилирует.

Надеюсь, что это поможет вам или, по крайней мере, оно поможет вам найти хороший ответ в отношении

...