.NET 程序集:理解类型可见性
我正在尝试重现 System.Xml.Serialization 已经执行的操作,但是针对不同的数据源。 目前任务仅限于反序列化。 即给定我知道如何阅读的已定义数据源。编写一个采用随机类型的库,通过反射了解它的字段/属性,然后生成并编译“reader”类,该类可以采用数据源和该随机类型的实例,并将数据源写入对象的字段/属性。
的简化摘录
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() + @"
}
}
}
";
}
}
这是我的 ReflectionHelper 类和调用代码
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 反序列化器以某种方式解决了这个问题 - 问题是:如何解决? 经过一个小时通过 Reflector 挖掘 System.Xml.Serialization 后,我开始承认我在这里缺乏某种基础知识,并且不确定我在寻找什么......
而且我完全有可能重新发明轮子和/或者挖错了方向,遇到这种情况请大声说出来!
I am trying to reproduce something that System.Xml.Serialization already does, but for a different source of data.
For now task is limited to deserialization only.
I.e. given defined source of data that I know how to read. Write a library that takes a random type, learns about it fields/properties via reflection, then generates and compiles "reader" class that can take data source and an instance of that random type and writes from data source into the object's fields/properties.
here is a simplified extract from my ReflectionHelper class
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() + @"
}
}
}
";
}
}
and the calling code:
class Program
{
static void Main(string[] args)
{
ReflectionHelper.GetFieldReader<Foo>();
Console.ReadKey(true);
}
private class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
}
The dynamic compilation of course fails because Foo class is not visible outside of Program class. But! The .NET XML deserializer somehow works around that - and the question is: How?
After an hour of digging System.Xml.Serialization via Reflector I came to accept that I lack some kind of basic knowledge here and not really sure what am I looking for...
Also it is entirely possible that I am reinventing a wheel and/or digging in a wrong direction, in which case please do speak up!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您不需要创建动态程序集并动态编译代码来反序列化对象。
XmlSerializer
也不这样做 - 它使用 Reflection API,特别是它使用以下简单概念:从任何类型检索字段集
Reflection 提供了
GetFields()
用于此目的的方法:我在此处包含
BindingFlags
参数,以确保它将包含非公共字段,否则默认情况下它将仅返回公共字段。设置任何类型中字段的值
Reflection 提供了用于此目的的函数
SetValue()
。您可以在FieldInfo
实例(从上面的GetFields()
返回)上调用此方法,并为其提供要在其中更改该字段值的实例,以及value 将其设置为:这基本上相当于
myObject.Field = myValue;
,当然,该字段是在运行时而不是编译时识别的。将它们放在一起
这是一个简单的例子。请注意,您需要进一步扩展它以处理更复杂的类型,例如数组。
请注意,我必须对 XDataReader 做出一些假设,最值得注意的是它只能读取这样的字符串。我确信您能够更改它,使其适合您的特定读者类别。
一旦您扩展了它以支持您需要的所有类型(包括示例类中的
int?
),您就可以通过调用来反序列化对象:即使
Foo< /code> 是私有类型,如您的示例中所示。
You don’t need to create a dynamic assembly and dynamically compile code in order to deserialise an object.
XmlSerializer
does not do that either — it uses the Reflection API, in particular it uses the following simple concepts:Retrieving the set of fields from any type
Reflection provides the
GetFields()
method for this purpose:I’m including the
BindingFlags
parameter here to ensure that it will include non-public fields, because otherwise it will return only public ones by default.Setting the value of a field in any type
Reflection provides the function
SetValue()
for this purpose. You call this on aFieldInfo
instance (which is returned fromGetFields()
above) and give it the instance in which you want to change the value of that field, and the value to set it to:This is basically equivalent to
myObject.Field = myValue;
, except of course that the field is identified at runtime instead of compile-time.Putting it all together
Here is a simple example. Notice you need to extend this further to work with more complex types such as arrays, for example.
Notice I had to make some assumptions about
XDataReader
, most notably that it can just read a string like that. I’m sure you’ll be able to change it so that it works with your particular reader class.Once you’ve extended this to support all the types you need (including
int?
in your example class), you can deserialize an object by calling:and you can do this even when
Foo
is a private type as it is in your example.如果我尝试使用 sgen.exe(独立的 XML 序列化程序集编译器),则会收到以下错误消息:
示例代码中调用
new XmlSerializer(typeof(Foo))
结果是:在 您认为 XmlSerializer 可以处理这个问题吗?
但是,请记住,在运行时,没有这样的限制。使用反射的可信代码可以自由地忽略访问修饰符。这就是 .NET 二进制序列化正在做的事情。
例如,如果您使用 DynamicMethod 在运行时生成 IL 代码,则您可以通过
skipVisibility = true
来避免对字段/类的可见性进行任何检查。If I try to use sgen.exe (the standalone XML serialization assembly compiler), I get the following error message:
Calling
new XmlSerializer(typeof(Foo))
in your example code results in:So what gave you the idea that XmlSerializer can handle this?
However, remember that at runtime, there are no such restrictions. Trusted code using reflection is free to ignore access modifiers. This is what .NET binary serialization is doing.
For example, if you generate IL code at runtime using DynamicMethod, then you can pass
skipVisibility = true
to avoid any checks for visibility of fields/classes.我已经在这方面做了一些工作。我不确定这是否有帮助,但无论如何我认为这可能是一种方式。最近,我对一个必须通过网络发送的类进行序列化和反序列化。由于有两个不同的程序(客户端和服务器),首先我在两个源中实现了该类,然后使用序列化。它失败了,因为 .Net 告诉我它没有相同的 ID(我不确定,但它是某种程序集 ID)。
嗯,经过一番谷歌搜索后,我发现这是因为序列化的类位于不同的程序集上,所以解决方案是将该类放在一个独立的库中,然后使用该库编译客户端和服务器。我对您的代码使用了相同的想法,因此我将 Foo 类和 FieldReader 类放在一个独立的库中,比方说:
编译它并将其添加到其他源(
using FooLibrary;
)是我用过的代码。它与你的不完全相同,因为我没有 GetCSharpName (我使用 t.Name 代替)和 XDataReader 的代码,所以我使用 IDataReader (只是为了让编译器接受代码并编译它)并更改 EntityField 顺便说一下,对于对象
,我发现了一个小错误,您应该使用 new 或重写 Fill 方法,因为它是抽象的。
好吧,我必须承认 GetFieldReader 返回 null,但至少编译器编译了它。
希望这会对您有所帮助,或者至少引导您找到好的答案
问候
I've been working a bit on this. I'm not sure if it will help but, anyway I think it could be the way. Recently I worked with Serialization and DeSerealization of a class I had to send over the network. As there were two different programs (the client and the server), at first I implemented the class in both sources and then used serialization. It failed as the .Net told me it had not the same ID (I'm not sure but it was some sort of assembly id).
Well, after googling a bit I found that it was because the serialized class was on different assemblies, so the solution was to put that class in a independent library and then compile both client and server with that library. I've used the same idea with your code, so I put both Foo class and FieldReader class in a independent library, let's say:
compile it and add it to the other source (
using FooLibrary;
)this is the code I've used. It's not exactly the same as yours, as I don't have the code for GetCSharpName (I used t.Name instead) and XDataReader, so I used IDataReader (just for the compiler to accept the code and compile it) and also change EntityField for object
by the way, I found a tiny mistake, you should use new or override with the Fill method, as it is abstract.
Well, I must admit that GetFieldReader returns null, but at least the compiler compiles it.
Hope that this will help you or at least it guides you to the good answer
regards