9

I know IEnumerable.ToList() is supposed to create a new List, but with the items pointing to the same original items in the IEnumerable, as discussed at ToList()-- Does it Create a New List?

However, I'm getting some strange behavior with my code using VS 2012; WPF; and .NET 4.0. It started when IEnumerable.SequenceEquals() seemed not to work as I expected. I dug around with my QuickWatch dialog, and, unbelievably, the following statement evaluates to false:

this.Items.First () == this.Items.ToList ()[ 0 ]

I even tried:

this.Items.ToList ().IndexOf(this.Items.First ())

which evaluated to -1.

Items is declared as a property on a WPF custom control, like so:

public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register (
        "Items", 
        typeof ( IEnumerable<UserLayoutType> ), 
        typeof ( UserLayoutSelectorControl ),
        new FrameworkPropertyMetadata ( null, FrameworkPropertyMetadataOptions.AffectsRender, UserLayoutSelectorControl.PropertyChanged ) );


public IEnumerable<UserLayoutType> Items
{
    get
    {
        return ( IEnumerable<UserLayoutType> ) this.GetValue ( UserLayoutSelectorControl.ItemsProperty );
    }
    set
    {    
        this.SetValue ( UserLayoutSelectorControl.ItemsProperty, value );                
    }
}

UserLayoutType is simply a class generated by the XSD tool, with the following declaration:

// 
// This source code was auto-generated by xsd, Version=4.0.30319.17929.
// 
namespace MyAssays.UserLayoutCore.UserLayoutUtility {
    using System.Xml.Serialization;


    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlRootAttribute("UserLayout", Namespace="", IsNullable=false)]
    public partial class UserLayoutType {

This are the methods on a factory class that create the UserLayoutType Items in the first place:

public static IEnumerable<UserLayoutType> CreateFromFolder ( string folderPath )
    {
        if (String.IsNullOrEmpty(folderPath))
            throw new ArgumentNullException("folderPath", "Folder path must not be null");

        var userLayoutXmlFilePaths = Directory.GetFiles ( folderPath ).Where ( filePath => filePath.EndsWith ( ".UserLayout.xml", StringComparison.InvariantCultureIgnoreCase ) );
        return userLayoutXmlFilePaths.Select(filePath => UserLayoutFactory.CreateFromFile(filePath));
    }

    public static UserLayoutType CreateFromFile ( string filePath )
    {
        using ( var stream = new StreamReader ( filePath ) )
        {
            return ( UserLayoutType ) new XmlSerializer ( typeof ( UserLayoutType ) ).Deserialize ( stream );
        }
    }

Anybody have any idea what is happening? See image below: enter image description here

Community
  • 1
  • 1
prmph
  • 7,616
  • 11
  • 37
  • 46
  • hi, please try the .Equals method, ie. this.Items.First().equals(this.Items.ToList ()[ 0 ]) – monkeyhouse Aug 26 '13 at 03:12
  • What is items declared to be? I think it's best to try to create a short simple piece of code that shows the issues that you're talking about. Then post the code in your question. Besides that you need to learn the difference between == and .Equals() – dcaswell Aug 26 '13 at 03:13
  • What is the runtime type of `this.Items`? There's no override on `Equals` or `==`? – Blorgbeard Aug 26 '13 at 03:13
  • 1
    Is it possible that `UserLayoutType` is a struct? – Alex Aug 26 '13 at 03:16
  • No, there is no override on Equals – prmph Aug 26 '13 at 03:18
  • @ user814064: I know the difference between Equals and ==. ToList is supposed to return items that refer to the same original items. I am checking for reference equality, not value quality. UserLayoutType is a class, not a structure, and so == should return true if the references point to the same object – prmph Aug 26 '13 at 03:21
  • What does `this.Items.GetType()` return? – Blorgbeard Aug 26 '13 at 03:22
  • When you say: var left = this.Items.First (); var right = this.Items.ToList ()[ 0 ]; // Do left and right look like they are same when you stop in the Visual Studio debugger>? – dcaswell Aug 26 '13 at 03:26
  • @ user814064: At the point I'm getting these issues, Items.GetType().Name returns "WhereSelectArrayIterator`2". And, yes, this.Items.First () seems to be equal to this.Items.ToList ()[ 0 ] when I step through. – prmph Aug 26 '13 at 03:34
  • @Alex: I have added the start of the declaration for the UserLayoutType to the main question – prmph Aug 26 '13 at 03:36
  • 1
    Could it be that your `Items` property is creating new objects each time it is called. What does the following evaluate to? `var left = this.Items.First(); var right = this.Items.First(); var result = left == right;` – Timothy Walters Aug 26 '13 at 03:44
  • @Timothy Walters: Interesting, result = left == right returns false – prmph Aug 26 '13 at 03:51
  • Perhaps that `WhereSelectArrayIterator` is doing something like `RealItems.Select(t => new SneakyWrapper(t))` – Blorgbeard Aug 26 '13 at 03:58
  • In the property getter is it possible to replace the cast to IEnumerable into use of "as"? If so, does that make a difference? – RenniePet Aug 26 '13 at 03:59
  • @TimothyWalters: Your comment got me thinking about the factory method that generates the UserLayoutType items in the first place. I changed the last line in IEnumerable CreateFromFolder(...) to return a list (using .ToList()) instead of an IEnumerable (since a List IS an IEnumerable, the interface does not change). However, with this change, result is now true. It seems the control was using the iterator on the XML files to re-generate the Items property on every get – prmph Aug 26 '13 at 04:01
  • 1
    And so when I use a List instead of a true IEnumerable as the initial value for the property, everything just works. This indicates something significant; the answers to the question at (stackoverflow.com/questions/2774099/…) will therefore need to be updated. In case where the IEnumerable is a true IEnumerable (i.e. not just a collection packaged an as enumerable), ToList() may NOT return the same object references as in the IEnumerable. It will return a new List of objects which may not be the same or even Equal to the objects yielded by the IEnumerable when it is enumerated again – prmph Aug 26 '13 at 04:30
  • @prmph You should post that as an answer to your own question, you can accept your own answer to a question after 2 days from when you first asked has passed. It may help a future visitor. – Scott Chamberlain Aug 26 '13 at 05:28

1 Answers1

7

The main, probable, cause for why you're seeing new objects from this is that the IEnumerable<T> is wrapping a generator, and not a materialized collection.

Here's a simple LINQPad program to demonstrate:

void Main()
{
    IEnumerable<string> collection =
        from index in Enumerable.Range(1, 10)
        select "Index=" + index;

    var list1 = collection.ToList();
    var list2 = collection.ToList();

    ReferenceEquals(list1[0], list2[0]).Dump();
}

This will print False.

It will do this because the act of enumerating over the collection (.ToList() in this case) will execute the deferred LINQ query, and since we're enumerating the collection twice, we execute it twice, producing different instances with the same values.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825