How do I get around this problem with an external lambda expression variable?

I play with PropertyDescriptor and ICustomTypeDescriptor ( yet ), trying to associate a WPF DataGrid with an object for which data is stored in a dictionary.

Since if you pass a list of Dictionary objects to WPF DataGrid, it will automatically generate columns based on the public dictionary properties (Comparer, Count, Keys and Values) of the Person Person subclass and implements ICustomTypeDescriptor.

ICustomTypeDescriptor defines a GetProperties method that returns a PropertyDescriptorCollection.

The PropertyDescriptor property is abstract, so you have to subclass it, I decided that I will have a constructor that accepts Func and Action parameters that delegate the receipt and setting of values ​​in the dictionary.

Then I create a PersonPropertyDescriptor for each key in the dictionary as follows:

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); } 

The problem is that each property gets its own Func and Action, but they all share the external variable s , although the DataGrid automatically generates columns for "ID", "FirstName", "LastName", "Age", "Gender", all they get and set against "Paul", which is the final value of the rest s in the foreach loop.

How can I guarantee that each delegate uses the required dictionary key, i.e. s value during instance creation of Func / Action?

Significant duty.


Here is the rest of my idea, I'm just experimenting here, these are not “real” classes ...

 // 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); } } 
+4
source share
4 answers

The Marc solution is, of course, correct, but I thought that I would expand WHY below. As most of us know, if you declare a variable in a for or foreach expression, it lives only as long as inside, which makes it look like a variable, the same as the variable declared in the statement block of such a statement, but it’s not .

To understand this better, do the following for-loop. Then I reformulate the "equivalent" loop in the form of while.

 for(int i = 0; i < list.Length; i++) { string val; list[i] = list[i]++; val = list[i].ToString(); Console.WriteLine(val); } 

This works in a while-form, as shown below: (this is not quite the same, because continue will act differently, but for the rules for defining the field of view it is the same)

 { int i = 0; while(i < list.Length) { { string val; list[i] = list[i]++; val = list[i].ToString(); Console.WriteLine(val); } i++; } } 

When “exploded” in this way, the scope becomes clearer, and you can understand why it always captures the same “s” value in your program and why the Marc solution shows where to place your variable so that it is unique each time.

+3
source

Simply:

  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); } 

With the variables captured, it is importantly announced here. Thus, declaring the captured variable inside the loop, you get another instance of the capture class for each iteration (the loop variable s , technically declared outside the loop).

+7
source

create a local copy of s inside the for loop and use this.

 for(string s in this.Keys) { string key = s; //... } 
+2
source

Source: https://habr.com/ru/post/1308078/


All Articles