2

I'm seeing a different behavior than I expect of List.First():

I build some test app which contains a list

 public abstract class GridTemplateModelBase : INotifyPropertyChanged, IDisposable
    {
Public ObservableCollection<ColumnModel> Columns
        {
            get
            {
                return _columns;
            }
            protected set
            {
                _columns = value;
                OnPropertyChanged();
            }
        }
}

The ColumnModel class contains a property named GroupIndex. I would like to choose particular item from Columns list and set the GroupIndex property through another class so I used the following code:

  //choose the list item I want to change
  var test = GridProfile.Columns.First(p => p.FieldName == item);
  //Change GroupIndex value
  test.GroupIndex = 1;

I would expect this code to change the GroupIndex value of the list item but it seems that the `Columns.First()' method return a copy of this list item. See the following images:

Initial GroupIndex of selected list item is -1

Initial GroupIndex of selected list item is -1

test parameter value from code above after GroupIndex value changing, as you can see it changed to 1

test parameter value from code above after GroupIndex value changing - as you can see it changed to 1

Value of selected list item after run the code above => still -1

Value of selected list item after run the code above - still -1

If the List.First() method returns a reference of selected list item, we had to see identical values for that list item and the test parameter from the code above but surprisingly this is not the case.

Does anyone can explain how exactly the Linq.First() method works?

Hoping my question is clear


Here is the ColumnModel class content:

public class ColumnModel : INotifyPropertyChanged
    {
        /// <summary>
        /// Initializes a new instance of the ColumnModel class.
        /// </summary>
        public ColumnModel()
        {
            AllowEdit = DefaultBoolean.True;
            SortIndex = -1;
            GroupIndex = -1;
        }


        private string _cellToolTip;
        private int _sortIndex;
        private ColumnSortOrder _sortOrdering;
        private bool _isReadOnly;
        private int _groupIndex;
        private bool _isVisible;
        private bool _fixedWidth;
        private double _minWidth;
        private double _width;
        private EditSettingsHorizontalAlignment _cellContentAlighnment;
        private ColumnProfile _profileTemplate;
        private string _fieldHeader;
        private string _fieldName;
        private DefaultBoolean allowEdit;


        // Specifies the name of a data source field to which the column is bound. 
        public string FieldName
        {
            get
            {
                return _fieldName;
            }
            set
            {                
                _fieldName = value;
                OnPropertyChanged();
            }
        }

        public string FieldHeader
        {
            get
            {
                return _fieldHeader;
            }
            set
            {
                _fieldHeader = value;
                OnPropertyChanged();
            }
        }

        // Specifies the type of template to load
        public ColumnProfile ProfileTemplate
        {
            get
            {
                return _profileTemplate;
            }
            set
            {
                _profileTemplate = value;
                OnPropertyChanged();
            }
        }

        public EditSettingsHorizontalAlignment CellContentAlighnment
        {
            get
            {
                return _cellContentAlighnment;
            }
            set
            {
                _cellContentAlighnment = value;
                OnPropertyChanged();
            }
        }

        public double Width
        {
            get
            {
                return _width;
            }
            set
            {
                _width = value;
                OnPropertyChanged();
            }
        }

        public double MinWidth
        {
            get
            {
                return _minWidth;
            }
            set
            {
                _minWidth = value;
                OnPropertyChanged();
            }
        }

        public bool FixedWidth
        {
            get
            {
                return _fixedWidth;
            }
            set
            {
                _fixedWidth = value;
                OnPropertyChanged();
            }
        }

        public bool IsVisible
        {
            get
            {
                return _isVisible;
            }
            set
            {
                _isVisible = value;
                OnPropertyChanged();
            }
        }

        public int GroupIndex
        {
            get
            {
                return _groupIndex;
            }
            set
            {
                _groupIndex = value;
                OnPropertyChanged();
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return _isReadOnly;
            }
            set
            {
                _isReadOnly = value;
                OnPropertyChanged();
            }
        }

        public ColumnSortOrder SortOrdering
        {
            get
            {
                return _sortOrdering;
            }
            set
            {
                _sortOrdering = value;
                OnPropertyChanged();
            }
        }


        public DefaultBoolean AllowEdit
        {
            get { return allowEdit; }
            set
            {
                allowEdit = value;
                OnPropertyChanged();
            }
        }

        public string CellToolTip
        {
            get
            {
                return _cellToolTip;
            }
            set
            {
                _cellToolTip = value;
                OnPropertyChanged();
            }
        }


        public int SortIndex
        {
            get
            {
                return _sortIndex;
            }
            set
            {
                _sortIndex = value;
                OnPropertyChanged();
            }
        }        
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Update: By your comments I'm understanding that List.First() returns reference type, probably my code contains an issue or my debugger might returns wrong result - will check it.

Here is First source code (credit: @Enigmativity):

public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
  if (source == null)
    throw Error.ArgumentNull("source");
  if (predicate == null)
    throw Error.ArgumentNull("predicate");
  foreach (TSource source1 in source)
  {
    if (predicate(source1))
      return source1;
  }
  throw Error.NoMatch();
}

Thank you

Ofir
  • 5,049
  • 5
  • 36
  • 61
  • I doubt that this will copy your object - I rather think that you might have a problem in your `GroupIndex` setter (does it really set `_groupIndex`)? – Random Dev Apr 10 '15 at 06:49
  • @CarstenKönig - yes the setter is fine, I double checked this – Ofir Apr 10 '15 at 06:52
  • Post the `ColumnModel` class. Could it actually be a `struct`? – Jon Egerton Apr 10 '15 at 06:54
  • @JonEgerton As I mentioned, it is a reference type - see my post - added `ColumnModel` class – Ofir Apr 10 '15 at 06:57
  • 1
    What is *selected list item*? As I see in your pictures, your code changed `test.GroupIndex` to `1`. Have you compared the `FieldName` properties? Maybe in your collection you have multiple items with the same `FieldName`? Maybe your *selected list item* is not in `GridProfile.Columns` at all. Your issue is impossible to solve for anyone else right now. `First()` does not create a copy, so your issue is outside of the code you posted. – sloth Apr 10 '15 at 07:01
  • is it just that the collection setter is not called when you change the first object in the list? do you need the propertyChanged call to be specified on an observalble collection? what is the backing type? – Ewan Apr 10 '15 at 07:36

4 Answers4

3

.First(x => ...) decompiles like this:

public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
  if (source == null)
    throw Error.ArgumentNull("source");
  if (predicate == null)
    throw Error.ArgumentNull("predicate");
  foreach (TSource source1 in source)
  {
    if (predicate(source1))
      return source1;
  }
  throw Error.NoMatch();
}

It's clearly returning the actual instance (if source is a reference type).

Something else must be happening in your code.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
2

According to this question and the selected answer, it depends on the value/reference type of the object:

The instances are the same if they are classes, but copies if they are structs/valuetypes.

Be careful with the information you see around lambda expressions in Visual Studio, debugging might not be 100% implemented correctly, based on the version you use. Not sure if that's the case here.

Community
  • 1
  • 1
MeanGreen
  • 3,098
  • 5
  • 37
  • 63
1

It returns the first element in the specified sequence, not new instance. So this is the original object stored in the collection. https://msdn.microsoft.com/en-us/library/vstudio/bb291976(v=vs.100).aspx Can you post ColumnModel class?

Radin Gospodinov
  • 2,313
  • 13
  • 14
0

IEnumerable<T>.First returns a reference to the item in the enumeration.

Nonetheless, depending on how GridProfile.Columns is implemented, you may get a new collection each time you call it. For example, if GridProfile.Columns = RawData.Select(p => new Column(p.param1, p.param2)) then each time you call it you will build a collection of newly created column objects and therefore the reference to the modified column will be lost.

To fix this, force the enumeration of the collection by invoking .ToList. This will prevent LINQ from re-iterating the Select(new ...) each time you call .Columns:

GridProfile.Columns = RawData.Select(p => new Column(p.param1, p.param2)).ToList
Ama
  • 1,373
  • 10
  • 24