属性に依存する「風雲再起」三


八.DependencyObjectテストコード


DependencyObjectテストコードを書く前に、次の図に示すように、どのようなメンバーと方法があるかを見てみましょう.
以上の図から,各種依存属性のGetValue,SetValue操作(コア機能),ClearValue,CoerceValue,GetLocalValue Enumerator,ReadLocalValueなどの操作が主な機能であることが分かる.これらの機能をテストするために、まずいくつかのクラスを作成し、最初のクラスX、内部に最初に追加の依存属性を登録します.追加の依存属性にしても依存属性にしても、GetValueとSetValue操作を使用する必要があります.1つは属性にカプセル化され、もう1つは静的な方法にカプセル化されています.2番目のクラスは、以前にDependencyPropertyを実装したときに作成したDependencyObjectプロトタイプクラスから直接継承されます.
   1: class X {
   2:     // A
   3:     public static readonly DependencyProperty AProperty = DependencyProperty.RegisterAttached("A", typeof(int), typeof(X));
   4:     // A 
   5:     public static void SetA(DependencyObject obj, int value)
   6:     {
   7:         obj.SetValue(AProperty, value);
   8:     }
   9:     // A 
  10:     public static int GetA(DependencyObject obj)
  11:     {
  12:         return (int)obj.GetValue(AProperty);
  13:     }
  14:     // B
  15:     public static readonly DependencyProperty BProperty = DependencyProperty.RegisterAttached("B", typeof(string), typeof(X));
  16:     // B 
  17:     public static void SetB(DependencyObject obj, string value)
  18:     {
  19:         obj.SetValue(BProperty, value);
  20:     }
  21:     // B 
  22:     public static string GetB(DependencyObject obj)
  23:     {
  24:         return (string)obj.GetValue(BProperty);
  25:     }
  26:  
  27: }
  28:  
  29: class Y : DependencyObject {
  30: }

3番目のクラスは、まずDependencyObjectプロトタイプクラスから継承される依存属性を直接テストして登録するためです.
   1: class Z : DependencyObject
   2:   {
   3:       public static readonly DependencyProperty SimpleDPProperty =
   4:          DependencyProperty.Register("SimpleDP", typeof(double), typeof(Z),
   5:              new PropertyMetadata((double)0.0,
   6:                  new PropertyChangedCallback(OnValueChanged),
   7:                  new CoerceValueCallback(CoerceValue)),
   8:                  new ValidateValueCallback(IsValidValue));
   9:  
  10:       public double SimpleDP
  11:       {
  12:           get { return (double)GetValue(SimpleDPProperty); }
  13:           set { SetValue(SimpleDPProperty, value); }
  14:       }
  15:  
  16:       private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  17:       {
  18:           Console.WriteLine(" , , : {0}", e.NewValue);
  19:       }
  20:  
  21:       private static object CoerceValue(DependencyObject d, object value)
  22:       {
  23:           Console.WriteLine(" , : {0}", value);
  24:           return value;
  25:       }
  26:  
  27:       private static bool IsValidValue(object value)
  28:       {
  29:           Console.WriteLine(" , True , : {0}", value);
  30:           return true;
  31:       }
  32:  
  33:   }

まずGetValueとSetValueの操作をテストするテストコードを書き、それから合格できません.最後にDependencyObjectクラスのGetValueとSetValueの方法をテスト例が合格するまで改善します.
   1: [Test]
   2: [Category ("NotWorking")]
   3: public void TestAttachedProperty()
   4: {
   5:     Y y1 = new Y();
   6:     X.SetA(y1, 2);
   7:     Assert.AreEqual(2, X.GetA(y1));
   8: }

ここではy 1とy 2の2つのオブジェクトであるため,それらのGetValueとSetValueもそれぞれの値を設定し取得する.
   1: [Test]
   2:     [Category ("NotWorking")]
   3:     public void Test2AttachedProperties()
   4:     {
   5:         Y y1 = new Y();
   6:         Y y2 = new Y();
   7:         X.SetA(y1, 2);
   8:         X.SetA(y2, 3);
   9:         Assert.AreEqual(2, X.GetA(y1));
  10:         Assert.AreEqual(3, X.GetA(y2));
  11:     }

前の図を見ると、DependencyObjectはローカル値列挙器を取得するGetLocalValueEnumeratorメソッドを提供しています.これはIEnumeratorを実現してLocalValueにアクセスしやすいようにしています.ここではそれを実現するために、テストコードを書きます.
   1: [Test]
   2:     [Category ("NotWorking")]
   3:     public void TestEnumerationOfAttachedProperties()
   4:     {
   5:         int count = 0;
   6:         Y y = new Y();
   7:         X.SetA(y, 2);
   8:         X.SetB(y, "Hi");
   9:  
  10:         // DependencyObject 
  11:         LocalValueEnumerator e = y.GetLocalValueEnumerator();
  12:         while (e.MoveNext()) {
  13:             count++;
  14:             if (e.Current.Property == X.AProperty)
  15:                 Assert.AreEqual(e.Current.Value, 2);
  16:             else if (e.Current.Property == X.BProperty)
  17:                 Assert.AreEqual(e.Current.Value, "Hi");
  18:             else
  19:                 Assert.Fail("Wrong sort of property" + e.Current.Property);
  20:         }
  21:         //count 2
  22:         Assert.AreEqual(2, count);
  23:     }

いくつかの機能がありますが、Monoも研究していない以上、私たちはその力を入れません.次に、さっき実現したDependencyObjectコードを見てみましょう.

九.DependencyObject実装コード


前述のテスト例では、DependencyObjectクラスの基本的な機能は完了していますが、いくつかのポイントに注意してください.
1,依存属性は結局DependencyObjectとDependencyPropertyがペアになってこそ真のDependencyPropertyと言える
2.Register、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnlyのいずれの操作も、DependencyObjectによってDependencyPropertyの値を操作します.つまり、DependencyObjectという外部インタフェースによって操作します.DependencyPropertyは登録と内部処理のみを担当し、外部インタフェースを担当しません.
3.DependencyObjectには、ReadLocalValue、GetLocalValueEnumerator、CoerceValue、ClearValueなど、LocalValueを操作するインタフェースがいくつか用意されています.
4,登録依存属性を登録する場合,実質的にDependencyObjectに関連するpropertyDeclarationsであり,Dictionary>タイプであるが,registerコードでは完全に関連していないので,私も悩んでいるので,マイクロソフトのBCLはそう実現していないことを皆さんと一緒に検討してほしい.
   1: using System.Collections.Generic;
   2: //using System.Windows.Threading;
   3:  
   4: namespace System.Windows 
   5: {
   6:     public class DependencyObject
   7:     {
   8:         // DependencyObject DependencyProperty DependencyProperty
   9:         private static Dictionarystring,DependencyProperty>> propertyDeclarations = new Dictionarystring,DependencyProperty>>();
  10:         // , DependencyProperty, object
  11:         private Dictionaryobject> properties = new Dictionaryobject>();
  12:  
  13:         // , DependencyObject IsSealed 
  14:         public bool IsSealed {
  15:             get { return false; }
  16:         }
  17:  
  18:         // DependencyObject DependencyObjectType
  19:         public DependencyObjectType DependencyObjectType { 
  20:             get { return DependencyObjectType.FromSystemType (GetType()); }
  21:         }
  22:  
  23:         // , 
  24:         public void ClearValue(DependencyProperty dp)
  25:         {
  26:             if (IsSealed)
  27:                 throw new InvalidOperationException ("Cannot manipulate property values on a sealed DependencyObject");
  28:  
  29:             properties[dp] = null;
  30:         }
  31:  
  32:         // DependencyPropertyKey, 
  33:         public void ClearValue(DependencyPropertyKey key)
  34:         {
  35:             ClearValue (key.DependencyProperty);
  36:         }
  37:  
  38:         // , 
  39:         public void CoerceValue (DependencyProperty dp)
  40:         {
  41:             PropertyMetadata pm = dp.GetMetadata (this);
  42:             if (pm.CoerceValueCallback != null)
  43:                 pm.CoerceValueCallback (this, GetValue (dp));
  44:         }
  45:  
  46:         public sealed override bool Equals (object obj)
  47:         {
  48:             throw new NotImplementedException("Equals");
  49:         }
  50:  
  51:         public sealed override int GetHashCode ()
  52:         {
  53:             throw new NotImplementedException("GetHashCode");
  54:         }
  55:  
  56:         // 
  57:         public LocalValueEnumerator GetLocalValueEnumerator()
  58:         {
  59:             return new LocalValueEnumerator(properties);
  60:         }
  61:  
  62:         // 
  63:         public object GetValue(DependencyProperty dp)
  64:         {
  65:             object val = properties[dp];
  66:             return val == null ? dp.DefaultMetadata.DefaultValue : val;
  67:         }
  68:         
  69:  
  70:         public void InvalidateProperty(DependencyProperty dp)
  71:         {
  72:             throw new NotImplementedException("InvalidateProperty(DependencyProperty dp)");
  73:         }
  74:         
  75:         // , 
  76:         protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
  77:         {
  78:             PropertyMetadata pm = e.Property.GetMetadata (this);
  79:             if (pm.PropertyChangedCallback != null)
  80:                 pm.PropertyChangedCallback (this, e);
  81:         }
  82:  
  83:         // LocalValue 
  84:         public object ReadLocalValue(DependencyProperty dp)
  85:         {
  86:             object val = properties[dp];
  87:             return val == null ? DependencyProperty.UnsetValue : val;
  88:         }
  89:  
  90:         // 
  91:         public void SetValue(DependencyProperty dp, object value)
  92:         {
  93:             if (IsSealed)
  94:                 throw new InvalidOperationException ("Cannot manipulate property values on a sealed DependencyObject");
  95:  
  96:             if (!dp.IsValidType (value))
  97:                 throw new ArgumentException ("value not of the correct type for this DependencyProperty");
  98:  
  99:             ValidateValueCallback validate = dp.ValidateValueCallback;
 100:             if (validate != null && !validate(value))
 101:                 throw new Exception("Value does not validate");
 102:             else
 103:                 properties[dp] = value;
 104:         }
 105:  
 106:         // DependencyPropertyKey 
 107:         public void SetValue(DependencyPropertyKey key, object value)
 108:         {
 109:             SetValue (key.DependencyProperty, value);
 110:         }
 111:  
 112:         protected virtual bool ShouldSerializeProperty (DependencyProperty dp)
 113:         {
 114:             throw new NotImplementedException ();
 115:         }
 116:  
 117:         // propertyDeclarations
 118:         internal static void register(Type t, DependencyProperty dp)
 119:         {
 120:             if (!propertyDeclarations.ContainsKey (t))
 121:                 propertyDeclarations[t] = new Dictionary<string,DependencyProperty>();
 122:             Dictionary<string,DependencyProperty> typeDeclarations = propertyDeclarations[t];
 123:             if (!typeDeclarations.ContainsKey(dp.Name))
 124:             {
 125:                 typeDeclarations[dp.Name] = dp;
 126:                 // , 
 127:             }
 128:             else
 129:                 throw new ArgumentException("A property named " + dp.Name + " already exists on " + t.Name);
 130:         }
 131:     }
 132: }

前のDependencyObjectとDependencyPropertyの研究を通じて、最も重要な役割を見てみましょう.これもマイクロソフトが最も好きな概念であるメタデータです.マイクロソフトBCLのソースコードを研究したことがあれば、CLR全体に貫かれていることを知っているはずです.