你好 我想给你展示一个使用
StructLayout的示例,它比带有字节,整型和其他原始类型的示例更有趣,但一切都非常明显。
在着手进行闪电般的封装违反之前,值得简要回顾一下StructLayout是什么。 严格来说,它甚至是
StructLayoutAttribute ,该属性使您可以创建类似于C ++中的union的结构和类。 此属性使您可以控制类成员在内存中的位置(使用偏移量)。 因此,它被放置在类之上。
通常,如果一个类有2个字段,我们希望它们按顺序排列,即它们将彼此独立(不重叠)。 但是,
StructLayout允许您指定字段的位置不是由环境而是由用户设置。 为了明确指定字段的偏移量,我们应该使用
LayoutKind.Explicit参数。
为了表明要从类/结构(以下简称为“类”)开始的偏移量,我们需要在其上放置
FieldOffset属性。 此属性将字节数(从类开头的偏移量)作为参数。 为了不破坏指向方法表或同步块索引的指针,不可能传递一个负值。 因此,它将变得更加复杂。
让我们开始编写代码。 首先,我建议从一个简单的例子开始。 创建以下类:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); }
接下来,我们使用上述机制来明确指定字段偏移量。
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; }
现在,我将推迟解释,并按如下所示使用书面课程:
class Program { static void Main(string[] args) { CustomStructWithLayout instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClass(); instance.Str = "4564"; Console.WriteLine(instance.SomeInstance.GetType()); //System.String Console.WriteLine(instance.SomeInstance.ToString()); //4564 Console.Read(); } }
调用
GetType()方法将返回一个字符串,
ToString()方法很顽皮,并为我们提供了字符串“ 4564”。
脑部放电:调用
CustomClass虚拟属性后会显示什么?
就像您已经猜到的那样,我们初始化了
CustomStructWithLayout ,两个链接都为null,然后初始化了我们类型的字段,然后将字符串分配给Str字段。 结果,
CustomClass链接没有指向
CustomClass对象,而是指向System.string对象(包括方法表和同步单元的索引)。 但是编译器看到该字段仍然是我们类的类型。
为了证明这一点,这里是WinDbg的一个小片段:

在这里您可以看到一些不寻常的东西。
- 在CustomStructWithLayout中,对象字段具有不同的方法表地址(非常期望),但是对象的地址相同。
- 第二个是,您可以看到两个字段都位于偏移量4处。我想大多数人都可以理解,但以防万一,我将向您解释,直接将对象的地址放置到方法表的链接。 字段以4字节的偏移量开始(用于32位),而同步块的索引以-4的偏移量定位。 因此,两个对象的偏移量相同。
现在您已经弄清楚了发生了什么,您可以尝试使用偏移量调用不应该调用的内容。
为此,我在一个类中重复了字符串类的结构。 但是我只重复了开头,因为类字符串非常庞大,而且我很懒。
注意:const和static并不是必需的,只是为了好玩。
public class CustomClassLikeString { public const int FakeAlignConst = 3; public const int FakeCharPtrAlignConst = 3; public static readonly object FakeStringEmpty; public char FakeFirstChar; public int FakeLength = 3; public const int FakeTrimBoth = 3; public const int FakeTrimHead = 3; public const int FakeTrimTail = 3; public CustomClassLikeString(){} public CustomClassLikeString(int a){} public CustomClassLikeString(byte a){} public CustomClassLikeString(short a){} public CustomClassLikeString(string a){} public CustomClassLikeString(uint a){} public CustomClassLikeString(ushort a){} public CustomClassLikeString(long a){ } public void Stub1() { } public virtual int CompareTo(object value) { return 800; } public virtual int CompareTo(string value) { return 801; } }
好吧,带有布局的结构将有所改变。
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; }
此外,当调用
FakeLength或
CompareTo()方法时,由于这些类成员相对于对象本身的地址的偏移量相同,因此将调用相应的字符串方法(在这种情况下)。
到达我可以使用的字符串的第一个私有方法太久了,所以我停在了一个公开的方法上。 但是领域是私有的,一切都是诚实的。 顺便说一句,方法是虚拟的,以防止任何干扰工作的优化(例如,嵌入),并且也使方法由方法表中的偏移量调用。
因此,性能。 显然,在调用不应调用且违反封装的事物方面的直接竞争者是反思。 我认为很明显,我们比这件事要快,因为我们不分析元数据。 确切值:
方法 | 工作机会 | 均值 | 失误 | 标准差 | 中位数 |
---|
StructLayoutField | 清除率 | 0.0597 ns | 0.0344 ns | 0.0396 ns | 0.0498 ns |
反射场 | 清除率 | 197.1257 ns | 1.9148 ns | 1.7911 ns | 197.4787 ns |
StructLayoutMethod | 清除率 | 3.5195 ns | 0.0382 ns | 0.0319 ns | 3.5285 ns |
反射法 | 清除率 | 743.9793 ns | 13.7378 ns | 12.8504 ns | 743.8471 ns |
这是一长段代码,介绍了我如何评估性能(如果有人需要):
代号 [ClrJob] [RPlotExporter, RankColumn] [InProcessAttribute] public class Benchmarking { private CustomStructWithLayout instance; private string str; [GlobalSetup] public void Setup() { instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClassLikeString(); instance.Str = "4564"; str = "4564"; } [Benchmark] public int StructLayoutField() { return instance.SomeInstance.FakeLength; } [Benchmark] public int ReflectionField() { return (int)typeof(string).GetField("m_stringLength", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(str); } [Benchmark] public int StructLayoutMethod() { return instance.SomeInstance.CompareTo("4564"); } [Benchmark] public int ReflectionMethod() { return (int)typeof(string).GetMethod("CompareTo", new[] { typeof(string) }).Invoke(str, new[] { "4564" }); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } }
俄语版