tags:

views:

261

answers:

4

I have a question about the result of LINQ and Lambda query. For example, I have the following codes:

class ClassA<T> {
     public string Name { get; set; }
     public T ObjectT { get; set; }
}

List<ClassA<T>> list;
// list is populated
// First way to get instance from list, reference type?
ClassA<T> instance1 = list.Select(x=> x).Where(x=>x.Name == "A").
  FirstOrDefault();
// Second way to clone or copy instance from the list
ClassA<T> instance2 = list.Select(x=> 
  new ClassA<T> { Name = x.Name, ObjectT = x.ObjectT}).
  Where( x=> x.Name = "A").FirstOrDefault();

It is obviously that instance2 is a clone or copy of an instance found in list. How about instance1? Is this one a new instance or just a reference to an instance in the list? If instance1 is an object reference to list's item, any change to its property may change the same object in the list. Is that right?

If that's the case and I don't want to have any implicit effect on objects in the list, I think I should use the second strategy. However, if I do want any changes in the retrieved instances also have the same changes in the list, I should use strategy 1. Not sure if my understanding is correct. Any comments?

+1  A: 

Yes, the first query doesn't clone the objects in the list, so instance will reference the actual object from list. The second query explicitly constructs a clone of the object.

Pavel Minaev
but a shallow copy, right? the ObjectT will still be a reference from the original instance..
Carl Hörberg
Yes, of course.
Pavel Minaev
+2  A: 

it will be a reference to the object in the list. What you could do, is create a copy using a Clone() function that returns a duplicate resulting in the code

list.Where(x => x.Name == "A").FirstOrDefault().Clone()

Note that Select(x => x) is unnecessary. Clone would be an extension method along the lines of

public static ClassA<T> Clone<T>(this ClassA<T> self) {
    if (self == null) return null;
    return new ClassA { 
        Name = self.Name,
        ObjectT = self.ObjectT
    }
}
Jimmy
Thank for your simplified version. However, for me it is really hard to read. Where clause does make it more explicit and easy to read. However, if codes afterward x => not-as-instance by default mean x return type, that's cool. Not sure which way to go.
David.Chu.ca
yeah I changed it back, because FirstOrDefault doesn't support that overload anyways.
Jimmy
won't that just create a shallow copy? ObjectT will still only be a reference..
Carl Hörberg
yes, just a shallow copy. assuming T is a reference type. Can't really do much about that, without more constraints on what T would be.
Jimmy
+1  A: 

Since your ClassA<T> is a class (not a struct), instance1 is a reference to the same object that is inside the list.

About the strategy, if you didn't want the users of your list to be able to mess around the list elements, I would suggest these alternatives:

  • Make ClassA<T> a struct. This way any query will return copy of the elements in the list;

  • Make ClassA<T> implement IClonable like below, and clone it before passing to a untrustworthy code.

Beware that the ObjectT property might be a class. So, even after you clone the ClassA<T> object, the ObjectT property will be a reference to the same object, and any user code will be able to modify it. Maybe, you'll need to clone ObjectT too;


class ClassA<T> : ICloneable
{
     public string Name { get; set; }
     public T ObjectT { get; set; }

     public ClassA<T> Clone()
     {
      return (ClassA<T>)this.MemberwiseClone();
     }

     object ICloneable.Clone()
     {
      return this.Clone();
     }
}


As suggested, you could use an extension method to avoid null reference problems. As you can see, there are several solutions to the problem. You should choose the one that better fit to your specific problem.

static class CloneableExt
{
    public static T CloneNull<T>(this T obj) where T : class, ICloneable
    {
     if (obj == null) return null;
     return (T)obj.Clone();
    }
}

EDIT 1: CloneableExt added

jpbochi
I think this way would be recommended, I waffled and changed to extension method to propagate nulls.
Jimmy
Indeed, the extension method has this advantage. In fact you could even make an extension to any `ICloneable` class.
jpbochi
A: 

What you do in your second example is a shallow copy, that is a new Instance but with its refernce type members still refensced.

To do it properly you should implement ICloneable:

class ClassA<T> : System.ICloneable where T : System.ICloneable
    {
        public string Name { get; set; }
        public T ObjT { get; set; }


        public object Clone()
        {
            var clone = new ClassA<T>();
            clone.Name = (string)Name.Clone();
            clone.ObjT = (T)ObjT.Clone();
            return clone;
        }
    }

and then

ClassA<T> instance2 = list.Where( x=> x.Name == "A").First().Clone();
Carl Hörberg