كيف يمكنني الالتفاف حول مشكلة المتغير الخارجي التعبير عن Lambda؟

StackOverflow https://stackoverflow.com/questions/2717377

سؤال

أنا ألعب مع 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/

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top