كيف يمكنني الالتفاف حول مشكلة المتغير الخارجي التعبير عن Lambda؟
-
01-10-2019 - |
سؤال
أنا ألعب مع PropertyDescriptor و iCustomTypedescriptor (ساكن) محاولة ربط بيانات WPF بكائن ، يتم تخزين البيانات من أجله في القاموس.
نظرًا لأنك إذا قمت بتمرير DataGrid WPF ، فإن قائمة كائنات القاموس ستقوم بإنشاء أعمدة استنادًا إلى الخصائص العامة للقاموس (المقارن ، العد ، المفاتيح والقيم) قاموس الفئات الفرعية الخاصة بي وتنفذ IcustomTypedescriptor.
يحدد IcustomTypedescriptor طريقة getProperties التي تُرجع propertyDescriptorCollection.
PropertyDescriptor مجردة ، لذا يتعين عليك الطبقة الفرعية ، فكنت أحسب أن لديّ مُنشئًا أخذ Func ومعلمات الإجراء التي تفوض الحصول على القيم في القاموس.
أقوم بعد ذلك بإنشاء شخص ما لكل مفتاح في القاموس مثل هذا:
foreach (string s in this.Keys)
{
var descriptor = new PersonPropertyDescriptor(
s,
new Func<object>(() => { return this[s]; }),
new Action<object>(o => { this[s] = o; }));
propList.Add(descriptor);
}
المشكلة هي أن كل خاصية تحصل على func وعملها ولكنها جميعها تشترك في المتغير الخارجي س لذا ، على الرغم من أن أعمدة DataGrid AutoGenerates لـ "ID" ، "FirstName" ، "LastName" ، "Age" ، "الجنس" يحصلون عليه جميعًا على "الجنس" الذي يمثل قيمة الاستراحة النهائية لـ س في حلقة foreach.
كيف يمكنني التأكد من أن كل مندوب يستخدم مفتاح القاموس المطلوب ، أي قيمة S في الوقت الذي يتم فيه إنشاء مثيل/الإجراء؟
مجبر كثيرا.
إليكم بقية فكرتي ، أنا فقط أجرب هنا هذه الفصول "الحقيقية" ...
// DataGrid binds to a People instance
public class People : List<Person>
{
public People()
{
this.Add(new Person());
}
}
public class Person : Dictionary<string, object>, ICustomTypeDescriptor
{
private static PropertyDescriptorCollection descriptors;
public Person()
{
this["ID"] = "201203";
this["FirstName"] = "Bud";
this["LastName"] = "Tree";
this["Age"] = 99;
this["Gender"] = "M";
}
//... other ICustomTypeDescriptor members...
public PropertyDescriptorCollection GetProperties()
{
if (descriptors == null)
{
var propList = new List<PropertyDescriptor>();
foreach (string s in this.Keys)
{
var descriptor = new PersonPropertyDescriptor(
s,
new Func<object>(() => { return this[s]; }),
new Action<object>(o => { this[s] = o; }));
propList.Add(descriptor);
}
descriptors = new PropertyDescriptorCollection(propList.ToArray());
}
return descriptors;
}
//... other other ICustomTypeDescriptor members...
}
public class PersonPropertyDescriptor : PropertyDescriptor
{
private Func<object> getFunc;
private Action<object> setAction;
public PersonPropertyDescriptor(string name, Func<object> getFunc, Action<object> setAction)
: base(name, null)
{
this.getFunc = getFunc;
this.setAction = setAction;
}
// other ... PropertyDescriptor members...
public override object GetValue(object component)
{
return getFunc();
}
public override void SetValue(object component, object value)
{
setAction(value);
}
}
المحلول
إن حل مارك صحيح بالطبع ، لكنني اعتقدت أنني سأتوسع على سبب أدناه. كما يعلم معظمنا ، إذا أعلنت متغيرًا في أ for
أو foreach
بيان ، إنه يعيش فقط ما هو ما بداخله ، مما يجعله يبدو مثل المتغير هو نفس المتغير الذي تم إعلانه في كتلة البيان لمثل هذا البيان ، ولكن هذا ليس صحيحًا.
لفهمها بشكل أفضل ، خذ ما يلي الحلقة. ثم سأعيد تحديد حلقة "ما يعادل" في شكل الوقت.
for(int i = 0; i < list.Length; i++)
{
string val;
list[i] = list[i]++;
val = list[i].ToString();
Console.WriteLine(val);
}
هذا يعمل في حين أن الشكل كما يلي: (ليس هو نفسه بالضبط ، لأنه continue
سوف يتصرف بشكل مختلف ، ولكن بالنسبة لقواعد النطاق ، فإن الأمر هو نفسه)
{
int i = 0;
while(i < list.Length)
{
{
string val;
list[i] = list[i]++;
val = list[i].ToString();
Console.WriteLine(val);
}
i++;
}
}
عندما يصبح "الانفجار" بهذه الطريقة ، يصبح نطاق المتغيرات أكثر وضوحًا ، ويمكنك أن ترى لماذا يلتقط دائمًا قيمة "S" في برنامجك ، ولماذا يوضح حل مارك مكان وضعه المتغير بحيث يكون ذلك فريدًا تم القبض عليه في كل مرة.
نصائح أخرى
ببساطة:
foreach (string s in this.Keys)
{
string copy = s;
var descriptor = new PersonPropertyDescriptor(
copy,
new Func<object>(() => { return this[copy]; }),
new Action<object>(o => { this[copy] = o; }));
propList.Add(descriptor);
}
مع المتغيرات التي تم التقاطها ، هو المكان الذي هو عليه أعلن هذا مهم. لذلك بإعلان المتغير الذي تم التقاطه داخل الحلقة ، يمكنك الحصول على مثيل مختلف لفئة الالتقاط لكل تكرار (متغير الحلقة ، s
, ، تم إعلانه تقنيًا الخارج الحلقة).
إنشاء نسخة محلية من s
داخل الخاص بك for
حلقة واستخدم ذلك.
for(string s in this.Keys) {
string key = s;
//...
}
للحصول على بعض الأفكار الإضافية حول هذه المسألة ، انظر
http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-bart-one/