我正在尝试重现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类在Program类之外是不可见的。但! .NET XML反序列化器以某种方式解决了这个问题 - 问题是:如何? 经过反射器挖掘System.Xml.Serialization一个小时后,我开始接受我在这里缺乏一些基本知识而不确定我在寻找什么......
我完全有可能重新发明一个方向盘和/或向错误的方向挖掘,在这种情况下请大声说出来!
答案 0 :(得分:1)
您不需要创建动态程序集并动态编译代码以反序列化对象。 XmlSerializer
也没有这样做 - 它使用Reflection API,特别是它使用以下简单的概念:
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
是私有类型,您也可以这样做。
答案 1 :(得分:0)
我一直在研究这个问题。我不确定它是否会有所帮助,但无论如何我认为它可能就是这样。最近我使用Serialization和DeSerealization这个我必须通过网络发送的类。由于有两个不同的程序(客户端和服务器),起初我在两个源中实现了类,然后使用了序列化。它失败了,因为.Net告诉我它没有相同的ID(我不确定,但它是某种汇编ID)。
嗯,谷歌搜索后我发现这是因为序列化的类在不同的程序集上,所以解决方案是将该类放在一个独立的库中,然后用该库编译客户端和服务器。我对你的代码使用了相同的想法,所以我将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;
)
这是我用过的代码。它与你的不完全相同,因为我没有GetCSharpName的代码(我使用的是t.Name)和XDataReader,所以我使用了IDataReader(只是让编译器接受代码并编译它)并且还改变了EntityField对象
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或覆盖Fill方法,因为它是抽象的。
好吧,我必须承认GetFieldReader返回null,但至少编译器会编译它。
希望这对您有所帮助,或者至少它会引导您找到好的答案 问候
答案 2 :(得分:0)
如果我尝试使用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二进制序列化正在做的事情。
例如,如果使用DynamicMethod在运行时生成IL代码,则可以传递skipVisibility = true
以避免对字段/类的可见性进行任何检查。