.NET中按引用封送与按值封送的区别

什么情况下我们需要 ,按引用封送或者按值封送对象?

首先,先简单说下AppDomain(应用程序域概念):

个人理解:大家都知道Window把每个正在运行的程序以进程隔离了,当一个程序崩溃时,别的正在运行的程序没有任何影响。

好现在转到.NET平台上来,现在有一软件是.NET开发的.

当这个软件启动时,底层开启一个进程,同时加载相应的CLR环境. 默认情况下CLR只会加载一个默认应用程序域.我们可以让他同时加载几个应用程序域,此时这个进程就有好几个应用程序域.应用程序域最主要的功能就是(类似于进程的作用只不过他把作用范围缩小了).他能保障在一个进程内同时加载应用程序域其中一个崩溃时。其他应用程序域中运行的代码环境不受影响.

多个应用程序域可以在一个进程中运行;但是,在应用程序域和线程之间没有一对一的关联。多个线程可以属于一个应用程序域,尽管给定的线程并不局限于一个应用程序域,但在任何给定时间,线程都在一个应用程序域中执行。

 

就应为这种隔离机制,可能有些人会想到我可以专门开启一个应用程序域作为这个软件需要的数据缓存地方(这种设计可能比较小,耦合也比较高。在.NET 2.0时代我们会使用Remoting 技术 虽然彼此之间又隔离了一个进程但是实际上 彼此之间的通讯还是要经过AppDomain 实现方式是一样的).

但是就是应为每个应用程序域之间是相互隔离的不能直接调用. 此时就要使用 按引用封送或者按值封送的你要得传输数据.

 

下面列数3个Demo写了一部分注释

  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.Reflection;
7 using System.Runtime.Remoting;
8
9 namespace ConsoleApplication7
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 Marshalling();
16 }
17
18 private static void Marshalling()
19 {
20 // 获取当前线程的应用程序域
21 AppDomain adCallingThreadDomain = Thread.GetDomain();
22
23 // 获取当前应用程序域友好名字
24 String callingDomainName = adCallingThreadDomain.FriendlyName;
25 Console.WriteLine("Default AppDomain's friendly name={0}", callingDomainName);
26
27 // 获取当前程序集入口点全名
28 String exeAssembly = Assembly.GetEntryAssembly().FullName;
29 Console.WriteLine("Main assembly={0}", exeAssembly);
30
31 // 在线程栈申明一个变量没有指向托管堆上任何变量
32 AppDomain ad2 = null;
33
34 // *** DEMO 1:跨应用程序域使用引用封送
35 Console.WriteLine("{0}Demo #1", Environment.NewLine);
36
37 // 创建一个新应用程序域
38 ad2 = AppDomain.CreateDomain("AD #2", null, null);
39 MarshalByRefType mbrt = null;
40
41 // 创建指定类型的新实例。形参指定定义类型的程序集以及类型的名称。
42 // 其实该实例是 AD #2 应用程序域 中 MarshalByRefType 类型的一个代理
43 mbrt = (MarshalByRefType)
44 ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);
45
46 Console.WriteLine("Type={0}", mbrt.GetType()); // CLR在类型上撒谎
47
48 // 检察该类型是不是一个真正的代理对象
49 Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));
50
51 // 看起来像是在 MarshalByRefType 上调用一个方法,实则不然
52 // 我们是在代理类型上调用一个方法 代理使 线程转至拥有对象的 那个AppDomain 并在真实的对象调用这个方法
53 mbrt.SomeMethod();
54
55 // 卸载应用程序域 AD #2
56 AppDomain.Unload(ad2);
57 // 此时 mbrt 引用一个有效的代理对象;代理对象引用一个无效的AppDomain
58
59 try
60 {
61 // 在代理对象上调用一个方法. 应为此时AppDomain 已经卸载所以抛出异常
62 mbrt.SomeMethod();
63 Console.WriteLine("Successful call.");
64 }
65 catch (AppDomainUnloadedException)
66 {
67 Console.WriteLine("Failed call.");
68 }
69
70
71 // *** DEMO 2: 跨应用程序域使用按值封送
72 Console.WriteLine("{0}Demo #2", Environment.NewLine);
73
74
75 ad2 = AppDomain.CreateDomain("AD #2", null, null);
76
77 mbrt = (MarshalByRefType)
78 ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);
79
80 // 对象的方法返回所返回对象一个副本
81 // 对象按值封送
82 MarshalByValType mbvt = mbrt.MethodWithReturn();
83
84 // 证明我们的到的不是一个代理对象
85 Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbvt));
86
87 // 此时实际上我们是在真正的 MarshalByValType 对象上调用方法
88 Console.WriteLine("Returned object created " + mbvt.ToString());
89
90
91 AppDomain.Unload(ad2);
92
93 try
94 {
95 // 我们在对象上调用对象 没有报错 足够证明我们是在真正的对象上调用方法
96 Console.WriteLine("Returned object created " + mbvt.ToString());
97 Console.WriteLine("Successful call.");
98 }
99 catch (AppDomainUnloadedException)
100 {
101 Console.WriteLine("Failed call.");
102 }
103
104
105 // DEMO 3: 使用不可以封送的类型 进行跨AppDomain
106 Console.WriteLine("{0}Demo #3", Environment.NewLine);
107
108 ad2 = AppDomain.CreateDomain("AD #2", null, null);
109
110 mbrt = (MarshalByRefType)
111 ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);
112
113 // 对象的方法返回一个不可封送的对象;抛出异常
114 NonMarshalableType nmt = mbrt.MethodArgAndReturn(callingDomainName);
115 }
116 }
117
118 //此类继承 MarshalByRefObject 跨应用程序域的话使用引用封送、
119 public sealed class MarshalByRefType : MarshalByRefObject
120 {
121 public MarshalByRefType()
122 {
123 Console.WriteLine("{0} ctor running in {1}",
124 this.GetType().ToString(), Thread.GetDomain().FriendlyName);
125 }
126
127 public void SomeMethod()
128 {
129 Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
130 }
131
132 public MarshalByValType MethodWithReturn()
133 {
134 Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
135 MarshalByValType t = new MarshalByValType();
136 return t;
137 }
138
139 public NonMarshalableType MethodArgAndReturn(String callingDomainName)
140 {
141 Console.WriteLine("Calling from '{0}' to '{1}'.",
142 callingDomainName, Thread.GetDomain().FriendlyName);
143 NonMarshalableType t = new NonMarshalableType();
144 return t;
145 }
146 }
147
148 // 可以按引用封送需要能序序列化
149 [Serializable]
150 public sealed class MarshalByValType : Object //MarshalByRefObject
151 {
152 private DateTime m_creationTime = DateTime.Now; // NOTE: DateTime is [Serializable]
153
154 public MarshalByValType()
155 {
156 Console.WriteLine("{0} ctor running in {1}, Created on {2:D}",
157 this.GetType().ToString(),
158 Thread.GetDomain().FriendlyName,
159 m_creationTime);
160 }
161
162 public override String ToString()
163 {
164 return m_creationTime.ToLongDateString();
165 }
166 }
167
168 // 即不继承MarshalByRefObject 也不能序列化
169 // [Serializable]
170 public sealed class NonMarshalableType : Object
171 {
172 public NonMarshalableType()
173 {
174 Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
175 }
176 }
177 }

 

44行每个Demo 都有。 他的意思是创建一个当前应用程序域与新建的应用程序域之间通讯的代理对象.(表面可以理解为就是你新建的那个AppDomain 上的对象,但是其实上并不是,他只是一个代理)

如何理解这个代理呢?

实际上源AppDomain想想目标AppDomain发送或者返回一个对象引用时,CLR会在目标AddDomain中定义一个代理类型.这个代理类型是从原始类型中元数据定义的.因此,他看起来和原始类型一样;有完全一样的实例成员(属性,事件和方法)。但是,实例字段不会成为代理类型的一部分。代理对象内部维护着一个真正的对象只不过编译器,能认得.(这部分 代理对象实际上并没有自己定义的类型 内部是怎么实现的 我也不清楚!)

Demo 1: 38行 创建了一个新的应用程序域。
              44行 创建了一个代理对象.

              46行 看到的输出像是对象本身但是实际上并不是(他只是代理对象)

              49行 可以验证出到底是不是代理对象

              53行 需要重点说下,当前线程运行到这边的时候 线程还在木人的应用程序域中 这行执行的时候他实际上是运行了代理对象 代理对象会调用目标AppDomain的SomeMethod()方法 此时线程执行了应用程序域的切换 验证了上面一句话:多个线程可以属于一个应用程序域,尽管给定的线程并不局限于一个应用程序域,但在任何给定时间,线程都在一个应用程序域中执行。

             62行 说明的是:在56行的时候已经在目标应用程序域给卸载了(注意:每当一次显示调用卸载应用程序域的时候会执行一次垃圾回收,这次垃圾回收并不是只回收第0代 是完全的回收)也就是说代理对象中的真是对象已经被垃圾后回收; 执行的时候会抛出异常。

 

Demo 2: 其他都一样关键是82行不同 代理对象在目标应用程序域中执行143 他实际上返回了一个真正的对象. 大家可以看在149行这个类是可序列化的 其实发生的事为:代理对象调用MethodWithReturn(); 在目标应用程序域中序列化了一个对象,然后线程切换到默认应用程序域中 把这个对象反序列化回来.成为一个真正的对象了,其中通讯的是2进制字节而已.

大家再看 56行 之前已经卸载了新的应用程序域而程序征程运行

 

Demo 3: 其实就是把其中通讯的可序列化特性给取消了 直接跑出异常了 对象不可序列化;

 

大家到现在可能还是会比较迷糊 其实要跨应用程序域通讯其中肯定要有一个按引用封送(也就是说代理对象) 可以理解为WCF的方法契约 没有这东西你怎么知道我要调用目标应用程序域中的那个方法呢.

那按值应用封送呢 大家可以理解为WCF的模型契约 就是说彼此之间通讯框架 大家可以看上面代码在150行同样可以继承MarshalByRefObject去通讯应为不这么说是效率问题; 通讯的数据可以使用值封送 但是通讯的方法是必须要用应用封送的

 

下面写个例子吧 引用封送的效率低;

   private sealed class MBRO : MarshalByRefObject { public Int32 x; }
private sealed class NonMBRO : Object { public Int32 x; }

private static void FieldAccessTiming()
{
const Int32 count = 10000000;
NonMBRO nonMbro = new NonMBRO();
MBRO mbro = new MBRO();

Int64 time = Stopwatch.GetTimestamp();
for (Int32 c = 0; c < count; c++) nonMbro.x++;
Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - time);

time = Stopwatch.GetTimestamp();
for (Int32 c = 0; c < count; c++) mbro.x++;
Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - time);
}

大家可以理解为区属性就是去反射!

 

转载于:https://www.cnblogs.com/skolley/archive/2011/12/26/2302217.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值