标签:turn 而不是 msdn 模式 send 一个 内存泄露 handle ctp
原文:WPF Binding实现自推——强烈要求拍砖
因为项目经常碰到Binding的问题,常常不能联动,所以才进行了分析,我解决方法的思路比较死板:定义问题 ->分解问题 ->初步解决问题 ->优化解法。在解决问题过程中是有收获的,所以分享下心得,也希望各位大大能够帮助小弟查验下思路是否正确,帮忙解解惑,在这里先谢过了。当然推理这段是和WPF无关的,先有WPF的做法才有WPF的,还请各位不要因为WPF而却步。
我们先从最简单的推断,建立2个类,见代码:
public class Class1 { public int Count1{get;set;} } public class Class2 { public int Count2{get;set;} } class Program { static void Main(string[] args) { Class1 class1 = new Class1(); Class2 class2 = new Class2(); //TODO: //********************************************** //填写内容补充代码,可修改Class1,Class2 //要求:Class1和Class2不能互相知道 //********************************************** class1.Count1 = 10; Console.WriteLine(class2.Count2);//希望输出 10 class2.Count2 = 20; Console.WriteLine(class1.Count1);//希望输出 20 } }
我们知道了需求修改class1.Count1的值,class2.Count2的值同时改变。修改class2.Count2的值class1.Count1也改变。
代码中要求不能互相知道的意思是:Class1类的实现中不能有Class2类,Class2类中不能包含Class1类,也就是两者不能耦合。
需要Count1或Count2属性更改后通知,就要给Class1和Class2各加个事件:
public class PropertyEventArgs : EventArgs { private string _propertyName; public PropertyEventArgs(string propertyName) { _propertyName = propertyName; } public string PropertyName { get { return _propertyName; } set { _propertyName = value; } } } public class Class1 { public event EventHandler<PropertyEventArgs> PropertyChanged; private int _count1; public int Count1 { get { return _count1; } set { if (_count1 != value) { _count1 = value; PropertyChanged(this, new PropertyEventArgs("Count1")); } } } }
class2同class1
题目要求class1,class2不能耦合,所以必然有个中间类来“包装”。
public static class InteropWarp { public static void ListenChanged(Class1 class1,string properyName1,Class2 class2,string properyName2) { class1.PropertyChanged += (sender, e) => { var newValue = sender.GetType().GetProperty(e.PropertyName).GetValue(sender, null); class2.GetType().GetProperty(properyName2).SetValue(class2,newValue,null); }; class2.PropertyChanged+=(sender, e) => { var newValue = sender.GetType().GetProperty(e.PropertyName).GetValue(sender, null); class1.GetType().GetProperty(properyName1).SetValue(class1, newValue, null); }; } }
在主函数中写下如下调用便可以完成题目要求了
InteropWarp.ListenChanged(class1, "Count1", class2, "Count2");
不过我们也可以看出这个包装类有许多的问题:
1.参数类型为具体类,规定死了class1或class2.
2.其中的事件注册,如果class1销毁了,class2的事件应该予以销毁,要不然还是依赖不能销毁,产生内存泄露。如下:
class1.Count1 = 10; Console.WriteLine(class2.Count2);//希望输出 10 class1 = null; GC.Collect(); class2.Count2 = 20;//运行这句class2注册的事件去通知class1,这时候我们希望报错,因为class1应该销毁,可实际并没有,说明class1并未销毁
3.使用反射,如果多个属性更改必然会有性能影响。
问题是一个个解决的,首先来看第一个问题,为什么会需要把参数定为具体类型?那是因为从具体的类上才能得到事件信息(class1,class2上才定义了事件),所以把事件抽出到接口或抽象类中便可以了。
public interface IPropertyChanged { event EventHandler<PropertyEventArgs> PropertyChanged; }
这样class1,class2继承这个接口实现具体就可以了。
不足:但这个也需要加个接口,需要在类中具体实现,每个类都加似乎有些烦。
第二问题,那就是需要在InteropWarp内部传递方法需要使用弱引用,结合第一点我们可能会改写如下:
public static void ListenChanged(IPropertyChanged class1, string properyName1, IPropertyChanged class2, string properyName2) { var class1WeakReference = new WeakReference(class1); var class2WeakReference = new WeakReference(class2); Action<WeakReference,string, WeakReference,string> registerChangedEvent = (getValueClass, listenedProperty, setValueClass, needChangedProperty) => { ((IPropertyChanged)getValueClass.Target).PropertyChanged += (sender, e) => { if (e.PropertyName == listenedProperty)//判断是是否是需要更新的属性名 { //得到新值 var newValue = sender.GetType().GetProperty(e.PropertyName).GetValue(sender, null); //把值赋入 if (setValueClass.Target != null) setValueClass.Target.GetType().GetProperty(needChangedProperty). SetValue(setValueClass.Target, newValue, null); } }; }; registerChangedEvent(class1WeakReference,properyName1, class2WeakReference,properyName2); registerChangedEvent(class2WeakReference,properyName2, class1WeakReference,properyName1); }
这样当
class1 = null; GC.Collect();
之后class1是这真的被销毁了,class1没了,class2为class1注册的事件自然也没有意义,也应该销毁。所以当执行事件的时候,我们还要判断如果class1WeakReference .Target为空的话,就把事件给注销掉:
var propertyChangedEvent = default(EventHandler<PropertyEventArgs>); propertyChangedEvent = (sender, e) => { if(setValueClass.Target == null) { //注销事件,注意propertyChangedEvent实际上是被闭包的,但在此处无影响 ((IPropertyChanged) getValueClass.Target).PropertyChanged -= propertyChangedEvent; } else if (e.PropertyName == listenedProperty)//判断需要更新的属性名 { //得到新值 var newValue = sender.GetType().GetProperty(e.PropertyName).GetValue(sender, null); //把值赋入 setValueClass.Target.GetType().GetProperty(needChangedProperty). SetValue(setValueClass.Target, newValue, null); } }; ((IPropertyChanged) getValueClass.Target).PropertyChanged += propertyChangedEvent;
这样便可以在执行事件时对class1进行判断决定是否要把事件给去掉。
不足:这个是在执行对应事件后进行处理,假如没有执行事件内存依旧浪费。
第三个问题,反射在大数据量,属性更换频繁的前提下自然是能不用则不用,那如若不用反射?我们用什么?Emit?确实有了Expression ,使得我们书写Emit已经很方便,在我们自己的使用过程中或许真的就这么做了,可WPF还有个XAML,XAML是解释性的,动态调用,解析字符串在这种条件下用Emit还不是“最省”的。那怎么才是最省的?
每个属性名对一个类来说都是唯一的,那样便可以把属性名当做一个字符串来处理,把字符串当做Key存放到一个键值对应的散列表中(如:HashTable),Value域放属性的值,属性的类型是多样的可以是int,long,DateTime等系统自带的,也可以是Employee、Order等自定义的类所以用object类型存放。并在类中写两个函数Get,Set来获取处理值。
public class Class1 : IPropertyChanged { protected Dictionary<string, object> _dic = new Dictionary<string, object>(); public event EventHandler<PropertyEventArgs> PropertyChanged; public object GetValue(string propertyName) { if (_dic[propertyName] == null) return 10; return _dic[propertyName]; } public void SetValue(string propertyName,object value) { int count = (int) value; if (propertyName == "Count1") { if (count > 100) count = 100; } else if (propertyName == "Count2") { if (count > 50) count = 50; } _dic[propertyName] = count; if (PropertyChanged != null) PropertyChanged(this, new PropertyEventArgs(propertyName)); } }这里的弊端我们已经看出来,在SetValue中if语句是如此的丑陋,假使GetValue也要一一校验返回的话……。
我们自然希望每个属性都有属于他们自己的处理方法,类中的SetValue,GetValue只要调用下就可以。
可一般属性都有Get,Set两个方法, Value域只是一块内存,要放两个函数就需要一对多的映射,反映到代码中就是需要添加一个类。
public class PropertyDefinition { protected object _value; public virtual object GetValue() { return _value; } public virtual void SetValue(object value) { _value = value; } } public class NamePropertyDefinition : PropertyDefinition { public override object GetValue() { if (Equals(_value,"Curry")) return "http://wwww.cnblogs.com/Curry"; return _value; } public override void SetValue(object value) { _value = value; } } public class Class1 : IPropertyChanged { private Dictionary<string, PropertyDefinition> _dic = new Dictionary<string, PropertyDefinition>(); public event EventHandler<PropertyEventArgs> PropertyChanged; public Class1() { RegisterPropery("Count1", new PropertyDefinition()); RegisterPropery("Name", new NamePropertyDefinition());//需要为每个属性定义类,很废品 } protected void RegisterPropery(string propertyName,PropertyDefinition definition) { _dic.Add(propertyName, definition); } public object GetValue(string propertyName) { return _dic[propertyName].GetValue(); } public void SetValue(string propertyName,object value) { _dic[propertyName].SetValue(value); if (PropertyChanged != null) PropertyChanged(this, new PropertyEventArgs(propertyName)); } }
这样的话确实类中的SetValue和GetValue方法是方便了,可每次都要为属性弄个新类,这点人性化太差。
假如我们把Get,Set的方法,通过委托传入就好了。让SetValue有个返回值,以便传回修改完的值。
public class PropertyDefinition { internal object _value; public PropertyDefinition(object defaultValue) { _value = defaultValue; } public PropertyDefinition(object defaultValue,Func<object, object, object> setFunc,Func<object> getFunc) { _value = defaultValue; SetValue = setFunc; GetValue = getFunc; } //三个object的顺序是 修改后的值,新值,旧值 public Func<object, object, object> SetValue { get; set; } public Func<object> GetValue { get; set; } } public class Class1 : IPropertyChanged { private Dictionary<string, PropertyDefinition> _dic = new Dictionary<string, PropertyDefinition>(); public event EventHandler<PropertyEventArgs> PropertyChanged; public Class1() { //不传委托,只传默认值 RegisterPropery("Name", new PropertyDefinition("Curry")); //传入委托 RegisterPropery("Count1", new PropertyDefinition(1,(oldValue,newValue)=> { int count = (int) newValue; if (count > 100 || count < 0) return oldValue; return count/10; },null)); } protected void RegisterPropery(string propertyName,PropertyDefinition definition) { _dic.Add(propertyName, definition); } public object GetValue(string propertyName) { if (_dic[propertyName].GetValue != null) return _dic[propertyName].GetValue(); return _dic[propertyName]._value; } public void SetValue(string propertyName,object value) { //可以用三元表达式 // _dic[propertyName]._value = _dic[propertyName].SetValue != null ? // _dic[propertyName].SetValue(_dic[propertyName]._value, value) : value; if(_dic[propertyName].SetValue!=null) { _dic[propertyName]._value = _dic[propertyName].SetValue(_dic[propertyName]._value, value); } else { _dic[propertyName]._value = value; } if (PropertyChanged != null) PropertyChanged(this, new PropertyEventArgs(propertyName)); } }
这似乎已经符合我们的需要了,可仔细考虑下每次返回都调用GetValue是否真的有必要呢?
当值传入的时候往往就已经决定了最后的结果,我们每次取值的时候都要GetValue进行处理实际上有些浪费效能。
所以我们把GetValue委托处理完的值,保存起来,用的时候直接拿那个值就可以了。以这种保存值的做法,我们也可以把第一次传入的值作为默认值,加一个变量保存下就可以了。把这些值都定义为internal 是为了更好封装性。
internal object _value; internal object _coerceValue; internal object _default;
当然你可以把这些会变化的值在放到一个类中, 比如ModifiedValue,使操作和数据分离的更彻底些,不过看上去有点像贫血模式囧。value可以理解为变化的基准值所以名为BaseValue。CoerceValue 是GetValue处理后的值。动静值的分离在这里提出有点仓促,实际很可能是为了在继承时准备的。
internal class ModifiedValue { internal object BaseValue { get; set; } internal object CoerceValue { get; set; } }
如果有defaultValue和ModifiedValue的话,也就是说一个固值,一个是动值,那么操作也可以分为固值操作和动值操作。
自然的我们会把眼光放在动值操作上,因为动值有两个BaseValue 和CoerceValue ,在有GetValue的前提下用CoerceValue ,没有的话用BaseValue。这个判断操作可以进行封装,让代码层次更清晰,毕竟分层的目的是为了让每个类,每个方法做自己对应的事。
internal struct EffectiveValueEntry { //为了获值统一 internal EffectiveValueEntry GetFlattenedEntry() { EffectiveValueEntry entry = new EffectiveValueEntry(); if(IsCoerced) { entry.ModifiedValue.BaseValue = this.ModifiedValue.CoerceValue; } else { entry.ModifiedValue.BaseValue = this.ModifiedValue.BaseValue; } return entry; } internal void SetCoercedValue(object value) { ModifiedValue.CoerceValue = value; this.IsCoerced = true; } internal void ResetCoercedValue() { if (this.IsCoerced) { ModifiedValue modifiedValue = this.ModifiedValue; modifiedValue.CoerceValue = null; this.IsCoerced = false; } } internal bool IsCoerced { get; private set; } private ModifiedValue _modifiedValue; internal ModifiedValue ModifiedValue { get { if (_modifiedValue == null) { _modifiedValue = new ModifiedValue(); } return _modifiedValue; } } }
其他几个函数都能明白什么意思,GetFlattenedEntry函数是为了外面调用得到值的时候统一获得,这样就不需要在外面进行什么IsCoerced的判断了,拿值都只需要取BaseValue即可。这样整个类就变为下面这个样子:
public class Class1 : IPropertyChanged { private Dictionary<string, PropertyDefinition> _dic = new Dictionary<string, PropertyDefinition>(); private Dictionary<string, EffectiveValueEntry> _entryDic = new Dictionary<string, EffectiveValueEntry>(); public event EventHandler<PropertyEventArgs> PropertyChanged; public Class1() { //不传委托,只传默认值 RegisterProperty("Name", new PropertyDefinition("Curry")); //传入委托 RegisterProperty("Count1", new PropertyDefinition(1,(oldValue,newValue)=> { int count = (int) newValue; if (count > 100 || count < 0) return oldValue; return count/10; },null)); } protected void RegisterProperty(string propertyName,PropertyDefinition definition) { _dic.Add(propertyName, definition); _entryDic.Add(propertyName, new EffectiveValueEntry()); } public object GetValue(string propertyName) { //统一获取 return _entryDic[propertyName].GetFlattenedEntry().ModifiedValue.BaseValue; } public void SetValue(string propertyName,object value) { if(_dic[propertyName].SetValue!=null) { var oldValue = _entryDic[propertyName].GetFlattenedEntry().ModifiedValue.BaseValue; var coercedValue = _dic[propertyName].SetValue(oldValue, value); //在经过了SetValue委托的调用后又进行了GetValue的调用 if (_dic[propertyName].GetValue != null) _entryDic[propertyName].SetCoercedValue(coercedValue); else //可以看出来 XXX.ModifiedValue.BaseValue 的赋值太繁琐 _entryDic[propertyName].ModifiedValue.BaseValue = coercedValue; } else { _entryDic[propertyName].ModifiedValue.BaseValue = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyEventArgs(propertyName)); } }
这里我们又看出了还有几个可以改进的地方:
以上修改应该较容易理解所以就不用代码列出了。
接下来就是类的继承,继承的类如何沿用原来的属性,如何Override父类的属性。这个问题其实也不是问题,面向对象本身就有继承,用来保存PropertyDefinition的DictionaryCLR就会自动继承,我们只要构造函数调用基类的构造函数,对RegisterProperty函数进行调用就可以了。说要Override基类的属性其实就是要替换PropertyDefinition,因为我们已经把EffectiveValueEntry提出来了,更改EffectiveValueEntry的BaseValue就可以,当然现在已经改造为Value了。
这个过程也很简单,只要写个OverrideProperty在构造函数里调用下就好了:
protected void OverrideProperty(string propertyName, PropertyDefinition definition) { //覆盖基类的PropertyDefinition Dictionary _dic[propertyName] = definition; //更改默认值 var newEntry = _entryDic[propertyName]; newEntry.Value = definition._default;//这里用_default是不应该的,我偷懒了,一般字段应该是对类内部而言的,对外要公开为属性 _entryDic[propertyName] = newEntry; }
这样确实就OK了,可有没发现基类的PropertyDefinition是不变的,下面不管派生多少个子类这都是不变的,除非子类调用了OverrideProperty方法来进行重载,因为PropertyDefinition中只有默认值、CoerceValueCallback这两个东西,理论上定义一次就可以了,可现在的做法却是每个派生都相当要拷贝一份,这明显是浪费内存的。
不足:基类的PropertyDefinition需要子类都拷贝一份,造成内存浪费。
优化点:基类的PropertyDefinition需要子类都拷贝一份,造成内存浪费。
我们乘热打铁先来看如何继承父类的属性而不浪费资源,所谓的浪费就在于每个基类都拷贝了一份PropertyDefinition,造成不会变动的PropertyDefinition有多份,PropertyDefinition为属性而生,要是每个属性对应的Type都是全局唯一的不就可以解决了。做个全局表把 属性名 + 类的Type作为Key(propertyName.GetHashCode() ^ type.GetHashCode()), 还是为了唯一,当把子项加入全局表时也需要是静态的方法。这个静态的方法应该放在谁身上呢?那个所谓的全局表又该放在何处?一个做法把他们都放在PropertyDefinition类中,理由是本身就是定义这个类型的。另一个做法是再定义一个类DependencyProperty存放全局表和注册方外存放属性名称等,并把PropertyDefinition改名为PropertyMetadata只存放数据,理由是数据和具体归属相分离。哪种做法好?这个就看项目的大小和PropertyDefinition本身的复杂度(因业务需求增加),当然后一种做法还可以避免因为属性名查找拼装而造成太多字符串垃圾(字符串缓存只在显示定义时有效)。
当我们把Class1类上的Count变为0时,所希望的只是那个操作过的Class1的属性发生变化,而不是每个Class1的Count都发生变化,所以对每个类的每个属性来说都有一个EffectiveValueEntry实例,但每个EffectiveValueEntry的默认值都需要从PropertyDefinition中拷贝下来, 常用方式一个是即时加载就是说在初始化类的时候就把父类的属性全都拷贝过来,另一个是延迟加载,在用的时候才去拷贝。一般的CLR属性都是一次完工的属于即时,但现在我们可以使用延迟加载,即在GetValue的时候先查看该属性是否需要继承是否已获得,如果没有拷贝过父类属性,才拷贝一份。在基类有大量属性,可使用并不多的情况下有很大优势。这里再费下口水,拷贝只拷贝PropertyDefinition中的默认值,CoerceValueCallback还是每次都去取,毕竟拷贝过来的值会变,委托不会变嘛。
代码就不罗列了,有兴趣的可以看看 一站式WPF--依赖属性(DependencyProperty)一
我有几次想把PropertyDefinition写成泛型,因为WPF传入默认值的时候在编译时是不会检查的,运行时报的错又很奇怪,比如定义了一个double类型的属性,可你默认值写100是错误的,因为是100是int类型,你需要写成100.0这样;可都由于EffectiveValueEntry的原因被打回来。
优化点:在执行对应事件后才把没有类依赖的事件去掉,假如没有执行事件内存依旧浪费。
这个怕的就是没有执行,被忽略。如果把这个事件加入一个全局表中,不时对这个全局表中的项目进行检查,发现没有用的就从全局表中剔除,当然这个做法有点耗费效率,而且也花费了一个全局表的资源,到底值得不值得就看使用场景了。
如声明: static List<WeakReference> _globleWeakReferenceList = new List<WeakReference>();
增加
globleWeakReferenceList.Add(WeakReference);
循环列表逐一判断
if(WeakReference.Target==null)
globleWeakReferenceList.Remove(WeakReference);
在具体运用的时候就不需要放入一个WeakReference这么死板,WeakReference可以写成XX类把WeakReference作为其中的属性,在那个类中写些委托,在委托中可以写用注册事件,发现所处对象的WeakReference为null时,可以使用另外一个委托来把注册事件销毁掉。
检查全局表的时机?这个可以在每次增加之后检查, 每次销毁之后检查,在运行完特定程序后检查。
优化点:属性变化通知的类需要加个接口,可这个接口却是和具体业务没有关系,总之耦合太强。
这个问题其实是仁者见仁,智者见智,有些人会说加个接口就接口,这样已经OK了,如果你要监听全部的属性变化,那肯定是这种方式了。如果你感觉这种方式类似对消息队列的判断,用起来并不那么舒服,你只需要对某个具体的属性进行监听。那么你可以为你的类做个容器,放到容器里用,用容器里的SetValue来操作数据的赋值工作,这样就可以在容器里做手脚,在SetValue方法中对象具体赋值前引发个事件或委托。注意以下只是演示写法。
public class Wrapper { //注意这里是临时写法,当对象销毁时,会有内存泄露 private static readonly Dictionary<object, Action<object>> _delegateList = new Dictionary<object, Action<object>>(); public void SetValue(object obj,string propertyName,object value) { //查看是否有 发生变化的委托,而且实际上还要判断属性名称 if (_delegateList.ContainsKey(obj)) _delegateList[obj](value); obj.GetType().GetProperty(propertyName).SetValue(obj, value,null); } //增加对属性改变的委托 public void AddValueChanged(object obj,string propertyName,Action<object> action) { if (_delegateList.ContainsKey(obj)) _delegateList[obj] = action; else _delegateList.Add(obj, action); } }
对于其中的内存泄露需要用优化二中的解法。
大家可能发现其中的属性赋值依然用的是反射,这个在上面我们改进之后应该是从SetValue的入口点进行赋值的。所以还需要再一层,通过某个容器把属性拿出来。
其实CLR中有个TypeDescriptor来做这方面的工作。不知您是否注意到了DependencyObject 上的标签:
[TypeDescriptionProvider(typeof(DependencyObjectProvider))] public class DependencyObject : DispatcherObject {
关于TypeDescriptor的用法,意义请看BlueDog的Dotnet专业组件开发揭密系列,我认为他讲的已经非常好,特别是他写的sample建议下载阅读。
这方面也可以看 Stephen Toub在MSDN杂志2005年4月和五月的 ICustomTypeDescriptor, Part 1和ICustomTypeDescriptor, Part 2,其中Part 2关于串行非一致性(sequential consistency),园子里Jeffrey Sun的Memory Reordering/Memory Model 及其对.NET的影响 及其文中链接上的文章给出了很好解释。
WPF中的具体应用你可以看熊力的WPF 谁玩谁呢(1)
在自己的项目中你还可以通过动态代理技术把属性中的方法封装下,达到一定的业务和框架分离。
当然WPF出彩的地方很多,每个人对出彩的概念也会有些不同。
在优化方法二中,我们说到一个清理全局表的时机,这里简单说下WeakEventManager,他在我们AddListener的时候便下了个
base.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new DispatcherOperationCallback(this.CleanupOperation), null);
CleanupOperation中有ScheduleCleanup方法来检查清理全局表(每个线程各有一个)。
刚得知原由的时候我真的有种拍案叫绝的冲动。
这个结论是怎么得出来的呢?
我们自己自定义一个继承于WeakEventManager类的时候,会重载他的StartListening和StopListening方法,当执行完点击Button的事件后,会发现他自动调用我们的StopListening方法,那他什么时候调用了我自己的方法来销毁事件?
看堆栈可以看到是用WeakEventTable的CleanupOperation方法,可是具体什么时候调用堆栈还是没有给出具体信息,用Reflector查看了下发现CleanupOperation的调用存在于WeakEventTable的ScheduleCleanup中,端出Windbg给对应函数下断点:
0:010> !name2ee WindowsBase.dll!MS.Internal.WeakEventTable.ScheduleCleanup
Module: 6f901000 (WindowsBase.dll)
Token: 0x06000a58
MethodDesc: 6f91b8d4
Name: MS.Internal.WeakEventTable.ScheduleCleanup()
JITTED Code Address: 6f9a2000
0:010> bp 6f9a2000
0:010> g
断到后看clr栈,就可知在AddListener时已做了“准备”。
0:000> !clrstack
OS Thread Id: 0x11c0 (0)
ESP EIP
0028e84c 6f9a2000 MS.Internal.WeakEventTable.ScheduleCleanup()
0028e850 6f9a1d23 System.Windows.WeakEventManager.ProtectedAddListener(System.Object, System.Windows.IWeakEventListener)
……
这个检查是在ContextIdle后做的也就是说是在数据加载完UI呈现完之后执行的,数据该销毁的都销毁了,这个时候检查可以说时机点是最好的。所以大家如果继承于WeakEventManager做类自定义AddListener时最好中调用下base.ScheduleCleanup();
这里注意下:Dispatcher.BeginInvoke是在有创建message-only window的前提下(在Dispatcher初始化时),所以你在普通的Console Application程序中使用WeakEventManager意义就没有原来的那么大。
最后说声对不起标题党了,本来我是希望能把WPF Bindng还原,但是后来发现以自己现在的实力还不行,还需要继续修炼。Bindng的联动我们想大家应该能了解个大概了,我没有拼装,也是希望不要束缚住大家的思想,有时候一个技术是多种技术的融合,WPF也一直对这部分内容进行着修改完善,所以发挥出我们自己的能动性才是关键。
对于BindingExpresstion 想向各位高手请教有什么深意,在框架中的Expression指的有哪些?具体应用场景是什么?因为我实在无法理解源码中DependencyObjectType的意图:
DTypes have 2 purposes:
More performant type operations (especially for Expressions that
rely heavily on type inspection)
Forces static constructors of base types to always run first. This
consistancy is necessary for components (such as Expressions) that
rely on static construction order for correctness.
标签:turn 而不是 msdn 模式 send 一个 内存泄露 handle ctp
原文地址:https://www.cnblogs.com/lonelyxmas/p/12819978.html