3

please help me understand what's going on in here and whether it should work like that ? I have a generic list of objects from a CMS:

For example List<MyCMS.Articles.Article> myArticles = articles.All;

Later I output the contents of the list in a JSON format (for CMS UI - table list).

Now a single record would include:

article.Title
article.Alias
article.Guid
article.Description
+
article.SeoProperties.TitleOverride
article.SeoProperties.H1Tag
article.StateProperties.IsActive
article.StateProperties.Channels

etc...

as you can see an Article object has an additional class property - with common properties (used on other object types in the CMS)

I also use a filter class that does some filter operations with LINQ on the collection to return me only articles within a certain channel, for example...

So the problem is that when I serialize the collection as JSON - there are only a few "columns" that I really need to display in my table list, while I have no need in other fields - especially, possibly long fields such as "description" (lazy loaded from file system), etc... - I serialize with DataContractJsonSerializer...

I need a way to control what fields will be included in the JSON result... What I do is I use reflection to set property values to null if I don't need the property and decorate class properties with [DataMember(IsRequired = false, EmitDefaultValue = false)] attributes... - it should work well - but - as soon as I go over (even cloned!!) collection of final objects to strip off the fields = set value to "null" - property value becomes null - application wide - in all collections of such objects... eh ?

Some demo code in here:

void Page_Load() {
        MyCms.Content.Games games = new MyCms.Content.Games();
        List<MyCms.Content.Games.Game> allGames = games.All;

        MyCms.Content.Games games2 = new MyCms.Content.Games();
        List<MyCms.Content.Games.Game> allGamesOther = games2.All;

        Response.Write("Total games: " + allGames.Count + "<br />");

        //This is our fields stripper - with result assigned to a new list
        List<MyCms.Content.Games.Game> completelyUnrelatedOtherIsolated_but_notSureList = RemoveUnusedFields(allGamesOther);

        List<MyCms.Content.Games.Game> gamesFiltered = allGames.Where(g=>g.GamingProperties.Software=="89070ef9-e115-4907-9996-6421e6013993").ToList();

        Response.Write("Filtered games: " + gamesFiltered.Count + "<br /><br />");

    }

    private List<MyCms.Content.Games.Game> RemoveUnusedFields(List<MyCms.Content.Games.Game> games)
    {
        List<MyCms.Content.Games.Game> result = new List<MyCms.Content.Games.Game>();

        if (games != null && games.Count > 0)
        {
            //Retrieve a list of current object properties
            List<string> myContentProperties = MyCms.Utils.GetContentProperties(games[0]);

            MyCms.PropertyReflector pF = new MyCms.PropertyReflector();

            foreach (MyCms.Content.Games.Game contentItem in games)
            {
                MyCms.Content.Games.Game myNewGame = (MyCms.Content.Games.Game)contentItem.Clone();
                myNewGame.Images = "wtf!"; //just to be sure we do set this stuff not only null

                pF.SetValue(myNewGame, "GamingProperties.Software", ""); //set one property to null for testing

                result.Add(myNewGame);

            }
        }

    return result;
}

Objects are set to their "Default values" (basically, null, in most cases) with this:

 private object GetDefaultValue(Type type)
        {
            if (type.IsValueType)
            {
                try
                {
                    return Activator.CreateInstance(type);
                }
                catch {
                    return null;
                }
            }

            return null;
        }
Denis
  • 4,718
  • 5
  • 18
  • 20
  • If I am not mistaken the problem is, basically, you set a property of a class to null, and the same property of other classes becomes null. Can you post some code? Maybe something here: 'what I do is I use reflection to set property values to null.' Something must be linking these instances to each other. Did you design the `MyCms.Article.Article` object? – Jamie Treworgy Jan 12 '11 at 13:16
  • Yes, I designed the class, got full control of that and I don't use any static fields anywhere... I very much tend to agree with your suspicion, it's like the property is getting set in the entire class type metadata app.-wide... (will post some code in a moment) – Denis Jan 12 '11 at 13:25
  • What's the implementation of Game.Clone()? – Jamie Treworgy Jan 12 '11 at 13:45
  • Can you give us details on MyCms.PropertyReflector SetValue method? – Luis Jan 12 '11 at 13:48
  • implementing an ICloneable interface within the Game class, with public object Clone() { return this.MemberwiseClone(); } – Denis Jan 12 '11 at 13:53
  • The MyCms.PropertyReflector is entirely based on this class (by Guy Mahieu): http://blog.guymahieu.com/wp-content/uploads/2007/02/propertyreflector.cs with an update of setting property to it's Default value when input is an empty string or null... – Denis Jan 12 '11 at 14:00
  • Yep, MemberwiseClone() is a shallow copy. This clown's set to evil! – Jamie Treworgy Jan 12 '11 at 14:30

2 Answers2

2

Quite probably your having trouble with differentiating between a shallow copy and a deep copy.

If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object.

With a deep copy when you clone an object and that object has a field of reference type, a new clone of that object is created and assigned to the field (vs just referencing the first object). Thus you have to completely different objects that share nothing.

That means if you're using clone and some of the properties are in fact subproperties (that is, properties of an instance inside the original object) you're changing it application wide, because you're acting upon a reference, not a new instance of the subobject.

You have more information about it in

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx

Jorge Córdoba
  • 51,063
  • 11
  • 80
  • 130
  • It seems like this is it - especially my issue is related to sub properties that are classes by themselves. I just have to review the best options for me to apply the deep copy (I was distracted from 'deep-copying' my objects by examples of serializing them as XML... :)) But I feel the key is here.. Thank you! I'll update and optimize my code and hopefully (guess, definietely) mark this as the answer. – Denis Jan 12 '11 at 13:52
  • But did I even have to clone it ? I mean - Clone was my attempt to somehow get away from properties being set application wide... but again, I understood your answer and btw thank you very much for the precise link with a description of this issue. – Denis Jan 12 '11 at 13:56
0

You could create a kind of modelview classes with the necessary fields and use something like Automapper to fill them. By this way, you will have a nice code, easy to maintain and highly customizable.

  • Dear Tiago, thanks for your answer, but thing is - while I could try and solve it with various workarounds - I just have to understand (or else how can I claim to be a web developer without a full understanding of the subject :) why the property is being set to null all over the place in completely unrelated variables that hold this object type... – Denis Jan 12 '11 at 13:26
  • That's how it is supposed to work - I have a list of known fields that I need in a form: private string[] RequiredFields = { "Guid", "Title", "GamingProperties.UrlDirectGame", "StateProperties.IsActive", "Alias" }; – Denis Jan 12 '11 at 13:31