C#: Creating list<T> and XmlSerializer

trevor1940
trevor1940 used Ask the Experts™
on
Hoping someone can help as I'm totally confused

I'm trying to create 2 similar but not identical XML files

In the code bellow I don't understand

  • Why I can't loop through the first lists I have  to create second lists to print out the name @ ##1

  • Why I get exception errors when trying to use XmlSerializer @##2

  • How do I make the XmlSerializer so I can feed it either list?

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace SplitXML
{
    class Program
    {
        // create two logs to hold each list
        static List<History> HistoryLog = new List<History>();
        static List<DownLoadLog> downloadlog = new List<DownLoadLog>();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            // Dummy varibles;

            int myID = 123;
            int FileCount = 234;
            string ModName = "Hello World";
            string OldDir = @"D:\Path\To\OldPath";
            string NewDir = @"D:\Path\To\NewPath";


            // make a loop just   to test
            // this is to simulate where the infomation will be
            // derived from  
            for (int i = 1; i < 5; i++)
            {
                // history log
                History history = new History();

                Child1 child1 = new Child1()
                {
                    Name = "My Collection" + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    Number = myID + i,
                    ImageCount = FileCount + i,
                    DownloadedImagesCount = 0,
                    Finished = "true"
                };
                history.Posts.Add(child1);
                HistoryLog.Add(history);
                
                // from file system

                DownLoadLog FileLog = new DownLoadLog();
                Child2 child2 = new Child2()
                {
                    Name = ModName + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    OldPath = OldDir + i,
                    NewPath = NewDir + i,
                };
                FileLog.Posts.Add(child2);
                downloadlog.Add(FileLog);

            }
            // See what there is before Saving
            // ##1
//            foreach (var item in downloadlog)
//            {
//                Severity Code    Description Project File Line    Suppression State
//Error CS1061  'Program.DownLoadLog' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'Program.DownLoadLog' could be found(are you missing a using directive or an assembly reference ?)	SplitXML D:\Vb\Test\SplitXML\SplitXML\Program.cs 69  Active

//                Console.WriteLine("DlLog Name {0}", item.Name);
//            }
            History result = new History();
            foreach (History history in HistoryLog.Distinct())
            {
                result.Posts.AddRange(history.Posts);
               
            }

            foreach (var item in result.Posts)
            {
                Console.WriteLine("HistoryLog Name {0}", item.Name);
            }

            DownLoadLog DlResults = new DownLoadLog();
            foreach (DownLoadLog Dll in downloadlog.Distinct())
            {
                DlResults.Posts.AddRange(Dll.Posts); 
            }

            foreach (var item in DlResults.Posts)
            {
                Console.WriteLine("DownloadLog Name {0}", item.Name);
            }
            //History.Save(@"K:\temp\EE_History\FileLog.xml", FileLog);
            History.Save(@"K:\temp\EE_History\HistoryLog.xml", result);
            Console.ReadLine();
        }

        
        // Eventually move to a separate class
        [XmlRoot(ElementName = "history", DataType = "string", IsNullable = true)]
        public class History
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();
            #region Saving

            internal static void Save(string file, History source)
            {
                var serializer = new XmlSerializer(typeof(History)); // ##2
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, source);
                }
            }

            #endregion
        }
        [XmlRoot(ElementName = "history", DataType = "string", IsNullable = true)]

        public class DownLoadLog
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();


        }

        public class Post
        {
            [XmlElement(ElementName = "name")]
            public string Name { get; set; }
            [XmlElement(ElementName = "url")]
            public string Url { get; set; }
            [XmlElement(ElementName = "id")]
            public int Id { get; set; }
        }
        public class Child1 : Post
        {
            [XmlElement(ElementName = "number")]
            public int Number { get; set; }
            [XmlElement(ElementName = "imageCount")]
            public int ImageCount { get; set; }
            [XmlElement(ElementName = "downloadedImagesCount")]
            public int DownloadedImagesCount { get; set; }
            [XmlElement(ElementName = "finished")]
            public string Finished { get; set; }
        }

        public class Child2 : Post
        {
            [XmlElement(ElementName = "OldPath")]
            public string OldPath { get; set; }
            [XmlElement(ElementName = "NewPath")]
            public string NewPath { get; set; }
        }

        }
    }

Open in new window

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Commented:
For ##1, you're getting an error because you have 2 levels of lists:

FileLog.Posts.Add(child2);
downloadlog.Add(FileLog);

So downloadlog doesn't contain a "child" like child2. Instead, downloadlog is a list of DownLoadLog entries, and each one of those contains a Posts list, which contains the "child" entries.  So your looping code would look like this:

foreach (var item in downloadlog)
{
 foreach(var postin item.Posts)
  {
   Console.WriteLine("DlLog Name {0}", post.Name);
 }
}

Now, I have to strongly recommend that you determine some formatting/naming conventions and you stick to them. Right now, your code is all over the place, which makes it hard to debug stuff.

You have DownLoadLog, which is a class type:
public class DownLoadLog

Open in new window


...then you have downloadlog, which is a List<> of DownLoadLog entries:
static List<DownLoadLog> downloadlog = new List<DownLoadLog>();

Open in new window


...then you have a variable called FileLog (which SOUNDS like a class type) which is an instance of DownloadLog:
DownLoadLog FileLog = new DownLoadLog();

Open in new window


...then you have a History class, but the List<> container for it is called HistoryLog:
static List<History> HistoryLog = new List<History>();

Open in new window


It gets confusing REALLY quickly. Make use of the renaming features in Visual Studio to rename variables and class names so they all follow the same conventions. A few basic suggestions:

1. Use variableName for private and local variables:
static List<History> historyLog = new List<History>();
static List<DownLoadLog> downloadLog = new List<DownLoadLog>();
DownLoadLog fileLog = new DownLoadLog();

Open in new window


2. Use a lower-case "L" in the middle of "DownLoadLog" => "DownloadLog" - try to use proper case to indicate a separate CONCEPT in your names. Imagine putting a space before each upper-case letter and see if it sounds right as a descriptive name: "Down Load Log" - nope, "download" is one word, so "Download Log" sounds better, so use "DownloadLog" as the class name.

3. Stay consistent with naming concepts. The DownloadLog and History class are very similar but you didn't call the second class HistoryLog. Names can help as mental identifiers when reading code, so I'd suggest renaming History to HistoryLog just so there's a mental association between it and DownloadLog.

4. When naming List<> variables, use plural names, like:
static List<HistoryLog> historyLogs = new List<HistoryLog>();
static List<DownloadLog> downloadLogs = new List<DownloadLog>();

Again, this helps when reading code and sets you up so you can use singular form when looping through the lists (again, making it easy to read and understand quickly):
foreach(var [b]historyLog[/b] in [b]historyLogs[/b])
{
}

Bear in mind that code is difficult for everyone to read, even for Neo. Your brain has to parse that code and understand it. While you're coding it might all make perfect sense to you but if you come back with a fresh mind after a day or a week or a month, your brain is starting over, and if you're not using consistent practices in how you code, it makes it a LOT harder to read. Pretend you're writing code for someone else and try to make it as easy to read for them as possible. 

Moving onto your second question (##2), it doesn't matter how similar HistoryLog and DownloadLog currently are. They are completely different classes in the eyes of C#. It would be like asking a racecar driver, "Hi, I have a potato in my garage - can you drive it?" The driver would look at you funny because a potato is not a car.

The driver doesn't know that the potato in your garage is actually a 2000-pound potato that has an engine and wheels and everything that is needed to be able to be driven. It might actually be able to be driven, but the driver doesn't care - he doesn't even bother looking at the potato because it sounds ridiculous to him.

The serializer is the same way - you're setting up the serializer to expect one specific kind of object - the History object:
var serializer = new XmlSerializer(typeof([b]HistoryLog[/b]));

So the serializer prepares itself by looking at the HistoryLog object and analyzes the properties, etc... So when you actually pass in a HistoryLog object later:
serializer.Serialize(writer, [b]source[/b]);
...the writer/serializer already knows exactly what to expect and what to do in order to convert that source object to XML.

However, if you try to give it a DownloadLog object, it's looking at it and just rejects it because it's not a History object, even if it happens to be similar to a HistoryLog object.

To resolve this and use one serializer for both, you would use class inheritance. Basically, you establish one "parent" class that has all the common properties like "Posts":
[code]        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();
        }

Open in new window

...and then your HistoryLog and DownloadLog classes would extend from that PostsContainer class:
        public class DownloadLog : PostsContainer
        {
          ...any DownloadLog-specific properties here...
        }

        public class HistoryLog : PostsContainer
        {
          ...any HistoryLog-specific properties here...
        }

Open in new window


Once you have that kind of structure set up, you can tell the Serializer that it will be serializing a PostsContainer parent class.

Think of it this way: A raven is a type of bird, and a hummingbird is a type of bird. Not all birds are ravens and not all birds are hummingbird. However, a lot of people go bird-watching, so they'll go watching for ANY bird, no matter if it's a raven or a hummingbird.

So when you serialize a PostsContainer class, the code can optionally choose to handle any child objects, too. So you start by adding serialization at the parent class:
        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();

            internal void Save(string file)
            {
                var serializer = new XmlSerializer(typeof(PostsContainer));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, this);
                }
            }
        }

Open in new window


At this point, if you try to call Save() on a HistoryLog or DownloadLog object, the method is -there- but the serializer will fail and complain that it didn't expect the sub-object type. The message will look something like this:
Inner Exception 1:
InvalidOperationException: The type Program+DownloadLog was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

Open in new window


The serializer is basically saying, "Yeah, DownloadLog might be a valid child/kind of PostsContainer class, but I only prepared myself for instances of the PostsContainer class. You have to tell me if you want to also handle child classes of the PostsContainer class."

To do that, it's really easy, you just use XmlInclude above the parent class to tell the serializer what other child classes to prepare for:
        [XmlInclude(typeof(HistoryLog))]
        [XmlInclude(typeof(DownloadLog))]
        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();

            internal void Save(string file)
            {
                var serializer = new XmlSerializer(typeof(PostsContainer));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, this);
                }
            }
        }

Open in new window


Now you can do something like:
var historyLog = new HistoryLog();
historyLog.Posts.Add(...a post here...);
historyLog.Save("historylog.xml");

var downloadLog = new DownloadLog();
downloadLog .Posts.Add(...a post here...);
downloadLog .Save("downloadlog.xml");

Open in new window


The resulting XML should look something like this:
<?xml version="1.0" encoding="utf-8"?>
<PostsContainer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="HistoryLog">
  <posts>...</posts>
  ...any other HistoryLog-specific properties here...
</PostsContainer>

Open in new window

...and...
<?xml version="1.0" encoding="utf-8"?>
<PostsContainer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="DownloadLog">
  <posts>...</posts>
  ...any other DownloadLog-specific properties here...
</PostsContainer>

Open in new window

Author

Commented:
Hi I see what you mean about naming conventions

As far as I can follow I've taking your comments on board

My understanding historyLogs  holds each HistoryLog

PostsContainer has Posts

HistoryLog inherits Posts

So to create a new post in historyLogs your create a HistoryLog which  inherits the properties of Posts and PostsContainer

Similarly for downloadLogs


However the looping to see what there is hasn't got an inner foreach loop the way you suggest I get the expected output in the console??

I'm unable to get the save method to work
As you wrote
// Gives 
Severity	Code	Description	Project	File	Line	Suppression State
Error	CS0120	An object reference is required for the non-static field, method, or property 'Program.PostsContainer.Save(string)'	SplitXML	D:\Vb\Test\SplitXML\SplitXML\Program.cs	77	Active

            PostsContainer.Save(@"K:\temp\EE_History\FileLog.xml");
// If you try and pass  it an object 
// Severity	Code	Description	Project	File	Line	Suppression State
Error	CS1501	No overload for method 'Save' takes 2 arguments	SplitXML	D:\Vb\Test\SplitXML\SplitXML\Program.cs	78	Active

            PostsContainer.Save(@"K:\temp\EE_History\HistoryLog.xml", historyLogs);

Open in new window



using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace SplitXML
{
    class Program
    {
        // create two logs to hold each list
        static List<HistoryLog> historyLogs = new List<HistoryLog>();
        static List<DownloadLog> downloadLogs = new List<DownloadLog>();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            // Dummy varibles;

            int myID = 123;
            int FileCount = 234;
            string ModName = "Hello World";
            string OldDir = @"D:\Path\To\OldPath";
            string NewDir = @"D:\Path\To\NewPath";


            // make a loop just   to test
            // this is to simulate where the infomation will be
            // derived from  
            for (int i = 1; i < 5; i++)
            {
                // history log
                var historyLog = new HistoryLog()
                {
                    Name = "My Collection" + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    Number = myID + i,
                    ImageCount = FileCount + i,
                    DownloadedImagesCount = 0,
                    Finished = "true"
                };
               
                historyLogs.Add(historyLog);

                // from file system
                var downloadLog = new DownloadLog()
                {
                    Name = ModName + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    OldPath = OldDir + i,
                    NewPath = NewDir + i,
                };

                downloadLogs.Add(downloadLog);

            }
            // See what there is before Saving

            foreach (var downloadLog in downloadLogs)
            {
              
                    Console.WriteLine("DlLog Name: {0} NewPath: {1} ", downloadLog.Name, downloadLog.NewPath);
              
            }

            foreach (var historyLog in historyLogs)
            {
                    Console.WriteLine("HistoryLog Name: {0} ImageCount: {1}", historyLog.Name, historyLog.ImageCount);
              
                
            }
            
            //PostsContainer.Save(@"K:\temp\EE_History\FileLog.xml");
            //PostsContainer.Save(@"K:\temp\EE_History\HistoryLog.xml", historyLogs);
            Console.ReadLine();
        }

        
        // Eventually move to a separate class
        [XmlRoot(ElementName = "history", DataType = "string", IsNullable = true)]
        [XmlInclude(typeof(HistoryLog))]
        [XmlInclude(typeof(DownloadLog))]
        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();

            internal void Save(string file)
            {
                var serializer = new XmlSerializer(typeof(PostsContainer));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, this);
                }
            }
        }

        // Post has common elements
        public class Post
        {
            [XmlElement(ElementName = "name")]
            public string Name { get; set; }
            [XmlElement(ElementName = "url")]
            public string Url { get; set; }
            [XmlElement(ElementName = "id")]
            public int Id { get; set; }
        }

        // has only history Elements Inherets post
        public class HistoryLog : Post
        {
            [XmlElement(ElementName = "number")]
            public int Number { get; set; }
            [XmlElement(ElementName = "imageCount")]
            public int ImageCount { get; set; }
            [XmlElement(ElementName = "downloadedImagesCount")]
            public int DownloadedImagesCount { get; set; }
            [XmlElement(ElementName = "finished")]
            public string Finished { get; set; }
        }

        // has only Download Elements Inherets post
        public class DownloadLog : Post
        {
            [XmlElement(ElementName = "OldPath")]
            public string OldPath { get; set; }
            [XmlElement(ElementName = "NewPath")]
            public string NewPath { get; set; }
        }

        }
    }

Open in new window

Commented:
You originally had a static method for Save, so you were passing in the list/data as a 2nd parameter:

            History.Save(@"K:\temp\EE_History\HistoryLog.xml", result);
            ...
            internal static void Save(string file, History source)
            {
               ...
            }

When saving, it's simpler to just make it a regular object method and make "this" the source. So if you look at my updated examples, you can see that you're just calling Save() on the object itself, and the only parameter necessary is the filename:

historyLog.Save("historylog.xml");

Regarding the looping question - I'm not sure I understand what you mean. Can you rephrase your question?
Become a Certified Penetration Testing Engineer

This CPTE Certified Penetration Testing Engineer course covers everything you need to know about becoming a Certified Penetration Testing Engineer. Career Path: Professional roles include Ethical Hackers, Security Consultants, System Administrators, and Chief Security Officers.

Author

Commented:
The looping question was more to confirm my understanding as your example had an internal (2) foreach loop whereas  I've only 1  

HistoryLog is the Object it has unique proprieties  it also inherits the  parent Post
historyLogs is the name of the list holding all the HistoryLog Object(s)  (This name is arbitrary but calling it Minions is daft)  


you can see that you're just calling Save() on the object itself

OK but

Error	CS0117	'Program.HistoryLog' does not contain a definition for 'Save'

Open in new window


PostsContainer has a definition for 'Save'  but  it needs to know which list to save

So I'm either creating the HistoryLog  Object wrong or I'm calling save wrong


In theory you could have multiple lists of HistoryLog how would save know which list is being saved  unless you tell it?

Commented:
1. Your HistoryLog and DownloadLog classes extended the Post class, not the PostsContainer class, so they are not inheriting the Save method in the PostsContainer class. That's why you're getting that error.

2. Yes there can be multiple objects - that's why the Save method serializes the "this" instance, which is a special variable that refers to the current instance of that object.

I'm getting the sense that you're sort of doing the learn-by-trying approach. If that's true, you should really take the time to read through a tutorial on object oriented programming. Make sure it covers object inheritance/extending. Otherwise you will not realize that there are lots of tools available to you that will greatly simplify your life.

Author

Commented:
I've reading Inheritance in C# and .NET

And this
csharp_inheritance

Which I can follow but when I try and implement the principles I get lost for insance

(if i've interpeted your eariler comments correctly I've got this)

        [XmlRoot(ElementName = "history", DataType = "string", IsNullable = true)]
        [XmlInclude(typeof(HistoryLog))]
        [XmlInclude(typeof(DownloadLog))]
        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();

            internal  void Save(string file)
            {
                var serializer = new XmlSerializer(typeof(PostsContainer));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, this);
                }
            }
        }

        // Post has common elements
        public class Post
        {
            [XmlElement(ElementName = "name")]
            public string Name { get; set; }   
            .......... other common stuff
        }

        // has only history Elements Inherets post
        public class HistoryLog : PostsContainer
            { stuff}

Open in new window



When i try and create a new HistoryLog

                // history log
                var historyLog = new HistoryLog();
                historyLog.Posts.Add(
                    Name = "My Collection" + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    Number = myID + i,
                    ImageCount = FileCount + i,
                    DownloadedImagesCount = 0,
                    Finished = "true"
               );
                historyLogs.Add(historyLog);

Open in new window


None of the properties are available

Author

Commented:
I've re read this again today I'm not much further forward and still unable save the XML files

However the way I've written this injunction with your comments  I'm concerned  even if  I could save the  XML files the XMl wouldn't be correct  as any HistoryLog-specific properties needs to be part of the post


Your earlier comment

The resulting XML should look something like this:

<?xml version="1.0" encoding="utf-8"?>
<PostsContainer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="HistoryLog">
  <posts>...</posts>
  ...any other HistoryLog-specific properties here...
</PostsContainer>

Open in new window


If the xml output only had 1 post that looks valid but this is likely to have multiple posts hence the foreach loop
So the XML needs to resemble something like

<?xml version="1.0"?>
<history>
    <post>
        <name>My Name 1</name>
        <url>https://www.example.com/Thread.php?post=post1.html</url>
        <id>123</id>
         <!--other HistoryLog-specific properties-->
         <number>123</number>
         <imageCount>123</imageCount>
         <downloadedImagesCount>123</downloadedImagesCount>
        <finished>true</finished>
 </post>
    <post>
        <name>My Name 2</name>
        <url>https://www.example.com/Thread.php?post=post2.html</url>
        <id>124</id>
         <number>124</number>
       <imageCount>124</imageCount>
      <downloadedImagesCount>124</downloadedImagesCount>
       <finished>true</finished>
    </post>
</history>

Open in new window




Latest version of my code can you show me how this needs to written to get the desired output as I'm flumexed

Thank You

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace SplitXML
{
    class Program
    {
        // create two logs to hold each list
        static List<HistoryLog> historyLogs = new List<HistoryLog>();
        static List<DownloadLog> downloadLogs = new List<DownloadLog>();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            // Dummy varibles;

            int myID = 123;
            int FileCount = 234;
            string ModName = "Hello World";
            string OldDir = @"D:\Path\To\OldPath";
            string NewDir = @"D:\Path\To\NewPath";


            // make a loop just   to test
            // this is to simulate where the infomation will be
            // derived from  
            for (int i = 1; i < 5; i++)
            {
                var NewPost = new Post()
                {
                    Name = "Hello Word " + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i
                };                
                // history log
                var historyLog = new HistoryLog()
                {
                    Number = myID + i,
                    ImageCount = FileCount + i,
                    DownloadedImagesCount = 0,
                    Finished = "true"
                 };

                historyLog.Posts.Add(NewPost);
                historyLogs.Add(historyLog);

                //    // from file system
                var downloadLog = new DownloadLog()
                {
                   
                    OldPath = OldDir + i,
                    NewPath = NewDir + i
                };
                downloadLog.Posts.Add(NewPost);
                downloadLogs.Add(downloadLog);


            } // end for i


            //// See what there is before Saving

            foreach (var downloadLog in downloadLogs)
            {

                Console.WriteLine("DlLog NewPath: {0} ",  downloadLog.NewPath);
                foreach (var post in downloadLog.Posts)
                {
                    Console.WriteLine("downloadLog Name: {0} ", post.Name);
                }



            }

            foreach (var historyLog in historyLogs)
            {
                Console.WriteLine("HistoryLog ImageCount: {0} ",  historyLog.ImageCount);
                foreach(var post in historyLog.Posts)
                {
                    Console.WriteLine("HistoryLog Name: {0} ", post.Name);
                }


            }

            
            //PostsContainer.Save(@"K:\temp\EE_History\FileLog.xml");
            //historyLogs.s s.Save(@"K:\temp\EE_History\HistoryLog.xml");
            Console.ReadLine();
        }

        
        // Eventually move to a separate class
        [XmlRoot(ElementName = "history", DataType = "string", IsNullable = true)]
        [XmlInclude(typeof(HistoryLog))]
        [XmlInclude(typeof(DownloadLog))]
        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();
            
            internal void Save(string file)
            {
                var serializer = new XmlSerializer(typeof(PostsContainer));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, this);
                }
            }
        }
        // Post has common elements
        public class Post
        {
            [XmlElement(ElementName = "name")]
            public string Name { get; set; }
            [XmlElement(ElementName = "url")]
            public string Url { get; set; }
            [XmlElement(ElementName = "id")]
            public int Id { get; set; }
        }


        // has only history Elements Inherets post
        public class HistoryLog : PostsContainer
        {
            [XmlElement(ElementName = "number")]
            public int Number { get; set; }
            [XmlElement(ElementName = "imageCount")]
            public int ImageCount { get; set; }
            [XmlElement(ElementName = "downloadedImagesCount")]
            public int DownloadedImagesCount { get; set; }
            [XmlElement(ElementName = "finished")]
            public string Finished { get; set; }
        }

        // has only Download Elements Inherets post
        public class DownloadLog : PostsContainer
        {
            [XmlElement(ElementName = "OldPath")]
            public string OldPath { get; set; }
            [XmlElement(ElementName = "NewPath")]
            public string NewPath { get; set; }
        }

        }
    }

Open in new window


Output
Hello World
DlLog NewPath: D:\Path\To\NewPath1
downloadLog Name: Hello Word 1
DlLog NewPath: D:\Path\To\NewPath2
downloadLog Name: Hello Word 2
DlLog NewPath: D:\Path\To\NewPath3
downloadLog Name: Hello Word 3
DlLog NewPath: D:\Path\To\NewPath4
downloadLog Name: Hello Word 4
HistoryLog ImageCount: 235
HistoryLog Name: Hello Word 1
HistoryLog ImageCount: 236
HistoryLog Name: Hello Word 2
HistoryLog ImageCount: 237
HistoryLog Name: Hello Word 3
HistoryLog ImageCount: 238
HistoryLog Name: Hello Word 4

Open in new window

Commented:
Okay, I think I might see the problem. It sounds like you're trying to write all the history logs to one file and all the download logs to another file.

Every individual INSTANCE of the HistoryLog or DownloadLog (because they are both PostsContainers) can contain multiple Post entries. However, you're building out another list of instances, where each instance only contains one post (even though it could contain more).

In other words, let's say you have 3 posts that should be saved as history logs. Right now, you are creating 3 HistoryLog instances, and putting one post into each one, and then store the 3 HistoryLog instances into a list, so the whole thing is a container-inside-a-container, which looks like the structure on the left:

2019-03-05_13-01-42.png
Alternatively, you could store the 3 posts into one HistoryLog instance (since each HistoryLog instance can store multiple Posts), and then save it that way (the right side of the above screenshot).

However, if you absolutely need to do a container-inside-a-container, then you'll have to go back to using a static method that serializes the entire list, like this:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace SplitXML
{
    public class Program
    {
        // create two logs to hold each list
        static List<HistoryLog> historyLogs = new List<HistoryLog>();
        static List<DownloadLog> downloadLogs = new List<DownloadLog>();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            // Dummy varibles;

            int myID = 123;
            int FileCount = 234;
            string ModName = "Hello World";
            string OldDir = @"D:\Path\To\OldPath";
            string NewDir = @"D:\Path\To\NewPath";




            // make a loop just   to test
            // this is to simulate where the infomation will be
            // derived from  
            for (int i = 1; i < 5; i++)
            {
                var NewPost = new Post()
                {
                    Name = "Hello Word " + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i
                };
                // history log
                var historyLog = new HistoryLog()
                {
                    Number = myID + i,
                    ImageCount = FileCount + i,
                    DownloadedImagesCount = 0,
                    Finished = "true"
                };

                historyLog.Posts.Add(NewPost);
                historyLogs.Add(historyLog);

                //    // from file system
                var downloadLog = new DownloadLog()
                {

                    OldPath = OldDir + i,
                    NewPath = NewDir + i
                };
                downloadLog.Posts.Add(NewPost);
                downloadLogs.Add(downloadLog);


            } // end for i


            //// See what there is before Saving

            foreach (var downloadLog in downloadLogs)
            {

                Console.WriteLine("DlLog NewPath: {0} ", downloadLog.NewPath);
                foreach (var post in downloadLog.Posts)
                {
                    Console.WriteLine("downloadLog Name: {0} ", post.Name);
                }



            }

            foreach (var historyLog in historyLogs)
            {
                Console.WriteLine("HistoryLog ImageCount: {0} ", historyLog.ImageCount);
                foreach (var post in historyLog.Posts)
                {
                    Console.WriteLine("HistoryLog Name: {0} ", post.Name);
                }


            }

            PostsContainer.SaveList("HistoryLog.xml", historyLogs);  // <-- ############################# CHANGES
            PostsContainer.SaveList("DownloadLog.xml", downloadLogs);  // <-- ############################# CHANGES

            Console.ReadLine();
        }


        // Eventually move to a separate class
        [XmlRoot(ElementName = "history", DataType = "string", IsNullable = true)]
        [XmlInclude(typeof(HistoryLog))]
        [XmlInclude(typeof(DownloadLog))]
        public class PostsContainer
        {
            [XmlElement(ElementName = "post")]
            public List<Post> Posts { get; set; } = new List<Post>();

            internal void Save(string file)
            {
                var serializer = new XmlSerializer(typeof(PostsContainer));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, this);
                }
            }

// ########################################################
//# START OF CHANGES
// ########################################################

            internal static void SaveList(string file, List<HistoryLog> list)
            {
                // Convert to List<PostsContainer>
                List<PostsContainer> toList = new List<PostsContainer>();
                toList.AddRange(list);
                SaveList(file, toList);
            }

            internal static void SaveList(string file, List<DownloadLog> list)
            {
                // Convert to List<PostsContainer>
                List<PostsContainer> toList = new List<PostsContainer>();
                toList.AddRange(list);
                SaveList(file, toList);
            }

            internal static void SaveList(string file, List<PostsContainer> list)
            {
                var serializer = new XmlSerializer(typeof(List<PostsContainer>));
                using (TextWriter writer = new StreamWriter(file))
                {
                    serializer.Serialize(writer, list);
                }
            }
// ########################################################
//# END OF CHANGES
// ########################################################
        }
        // Post has common elements
        public class Post
        {
            [XmlElement(ElementName = "name")]
            public string Name { get; set; }
            [XmlElement(ElementName = "url")]
            public string Url { get; set; }
            [XmlElement(ElementName = "id")]
            public int Id { get; set; }
        }


        // has only history Elements Inherets post
        public class HistoryLog : PostsContainer
        {
            [XmlElement(ElementName = "number")]
            public int Number { get; set; }
            [XmlElement(ElementName = "imageCount")]
            public int ImageCount { get; set; }
            [XmlElement(ElementName = "downloadedImagesCount")]
            public int DownloadedImagesCount { get; set; }
            [XmlElement(ElementName = "finished")]
            public string Finished { get; set; }
        }

        // has only Download Elements Inherets post
        public class DownloadLog : PostsContainer
        {
            [XmlElement(ElementName = "OldPath")]
            public string OldPath { get; set; }
            [XmlElement(ElementName = "NewPath")]
            public string NewPath { get; set; }
        }

    }
}

Open in new window

Author

Commented:
It sounds like you're trying to write all the history logs to one file and all the download logs to another file

Yes and I need the output similar to my last comment which I'm guessing is closer to your right hand graphic

Currently I'm doing this as per your suggestion

 public class HistoryLog : PostsContainer

Open in new window


This means the properties of post are at a different level  to historyLog something like this
HistoryLog		
	Number	
	ImageCount	
	DownloadedImagesCount	
	Finished	
	Post	
		Name
		Url
		Id

Open in new window


What I actually need is for all the properties to be at the same level inside post  like this

HistoryLog		
	Post	
		Name
		Url
		Id
		Number
		ImageCount
		DownloadedImagesCount
		Finished
	\Post	
	Post	
		Name
		Url
		Id
		Number
		ImageCount
		DownloadedImagesCount
		Finished
	\Post	

Open in new window

Author

Commented:
Hi
Thanx very much

I think, with your considerable help, I've solved this or at least I'm producing XML files in the required format and structure

For completeness Here's what I've come up with

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

class MainClass
{
    class Program
    {


        // create two logs to hold each list
        
        public static List<HistoryLog> historyLogs = new List<HistoryLog>();
        static List<DownloadLog> downloadLogs = new List<DownloadLog>();

        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            // Dummy varibles;

            int myID = 123;
            int FileCount = 234;
            string ModName = "Hello World";
            string OldDir = @"D:\Path\To\OldPath";
            string NewDir = @"D:\Path\To\NewPath";


            // make a loop just   to test
            // this is to simulate where the infomation will be
            // derived from  with some condition deturmining if 
            // HistoryLog or DownloadLog is populated
            for (int i = 1; i < 5; i++)
            {

                // history log

                var historyLog = new HistoryLog()
                {
                    Name = "Hello Word " + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    Number = myID + i,
                    ImageCount = FileCount + i,
                    DownloadedImagesCount = 0,
                    Finished = "true"
                };

                historyLogs.Add(historyLog);

                // downloadLog

                var downloadLog = new DownloadLog()
                {

                    Name = ModName + i,
                    Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
                    Id = myID + i,
                    OldPath = OldDir + i,
                    NewPath = NewDir + i
                };

                downloadLogs.Add(downloadLog);
            }
            // See what there is before Saving
            // print 1 element from PostParent and historyLog

            foreach (var historyLog in historyLogs)
            {
                Console.WriteLine("HistoryLog Name: {0} HistoryLog ImageCount: {1}  ", historyLog.Name, historyLog.ImageCount);


            }

            foreach (var downloadLog in downloadLogs)
            {
                Console.WriteLine("download Name: {0} download NewPath: {1}  ", downloadLog.Name, downloadLog.NewPath);


            }



            try
            {
                // Save the XML  
                historyLogs.ToXml("history", "HistoryLog.xml");
                downloadLogs.ToXml("history", "DownloadLog.xml");
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
            }
            finally
            {
                Console.WriteLine("Good bye, crule World!");
            }
            Console.ReadLine();
        }


    }
}

// PostParent has common elements
// named PostParent as the xml Node name is post
public class PostParent
{
    [XmlElement(ElementName = "name")]
    public string Name { get; set; }
    [XmlElement(ElementName = "url")]
    public string Url { get; set; }
    [XmlElement(ElementName = "id")]
    public int Id { get; set; }
}

// has only history Elements Inherets post
// set the node name
[XmlType(TypeName="post")]
public class HistoryLog : PostParent
{
    [XmlElement(ElementName = "number")]
    public int Number { get; set; }
    [XmlElement(ElementName = "imageCount")]
    public int ImageCount { get; set; }
    [XmlElement(ElementName = "downloadedImagesCount")]
    public int DownloadedImagesCount { get; set; }
    [XmlElement(ElementName = "finished")]
    public string Finished { get; set; }
}

// has only Download Elements Inherets post
// set the node name
[XmlType(TypeName="post")]
public class DownloadLog : PostParent
{
    [XmlElement(ElementName = "OldPath")]
    public string OldPath { get; set; }
    [XmlElement(ElementName = "NewPath")]
    public string NewPath { get; set; }
}

// Universal XmlSerializer works on the object takes rootname and File Path
public static class SaveUtility
{
    public static void ToXml<T>(this T obj, string rootName, string xmlFile)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));

      // can be set if required
        var xmlNs = new XmlSerializerNamespaces();
        xmlNs.Add(string.Empty, string.Empty);

        using (TextWriter writer = new StreamWriter(xmlFile))
        {
            serializer.Serialize(writer, obj, xmlNs);
        }

    }
}

Open in new window

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial