How I failed when I tried to implement C# Generics to enable serializing and saving a variety of C# object types to disk for Windows Store applications

 

How I failed when I tried to implement C# Generics to enable serializing and saving a variety of C# object types to disk for Windows Store applications

Rate This
  • Comments 4

Introduction

  1. I wanted to take some code and make it more general purpose

  2. C# generics didn’t quite cut it.

  3. I wanted a generic routine that could save objects with different properties

  4. But I didn’t want ugly, “hard to comprehend in 3 months from now” code

  5. The traditional approach is to use Interfaces to guarantee that templated objects support the same methods

  6. But I wanted to make objects generic that had different properties (An impossibility as far as I could tell)

  7. If you want to support objects that have different properties, I couldn’t find an elegant way to do it

    1. See lines 42 to 45 of part 2 of the code
    2. T2 needs to have a property called FolderName and Folder
    3. So unless my template objects have the same properties, I am out of luck
    4. I can see why the compiler could complain
    5. But I am will to sacrifice compile time safety in favor of code re-use
  8. The code speaks for itself so there is barely any narrative here

  9. I present the before and after

  10. I’m sure many of you will find a better way and I look forward to seeing the comments

  11. There is use of delegates so that even a function call can be abstracted out of the code

  12. Some of code can be found here for serialization

    1. http://code.msdn.microsoft.com/windowsapps/CSWinStoreAppSaveCollection-bed5d6e6/view/SourceCode
  13. The code can serialize and save a collection of objects 

  14. The collection is composed of string paths to folders (I couldn’t save the StorageFolder object by itself)

Non-Generic Version

  1. This the "before" version.

  2. It makes NO USE OF GENERICS

Non-Generics Version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//////////////////////////////////////////////////////////////////////
//
// Non-generic code
//
//
async private void SaveSearch_Click(object sender, RoutedEventArgs e)
{
    await SaveToDisk();

}
async private void OpenSearch_Click(object sender, RoutedEventArgs e)
{
    await ReadFromFile();
}


private static async Task SaveToDisk()
{
    // Creat a save-able object list
    List<FoldersItemDisk> dataToSave = new List<FoldersItemDisk>();
    foreach (FoldersItem item in MyGlobals.itemsFoldersItems)
    {
        dataToSave.Add(new FoldersItemDisk { FolderName = item.FolderName });
    }

    // Make xml out of it
    string localData = ObjectSerializer<List<FoldersItemDisk>>.ToXml(dataToSave);

    // Save to disk part 1
    StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
    StorageFile storageFile =
            await storageFolder.CreateFileAsync("Results1.dat", CreationCollisionOption.ReplaceExisting);

    // Save to disk part 2
    using (IRandomAccessStream stream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
    {
        using (DataWriter dataWriter = new DataWriter(stream))
        {
            dataWriter.WriteString(localData);
            await dataWriter.StoreAsync();
        }
    }
}

private async Task ReadFromFile()
{

    try
    {

        StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
        StorageFile storageFile =
                await storageFolder.GetFileAsync("Results1.dat");

        using (IRandomAccessStreamWithContentType readStream = await storageFile.OpenReadAsync())
        using (DataReader reader = new DataReader(readStream))
        {
            ulong streamSize = readStream.Size;
            UInt32 totalBytesRead = await reader.LoadAsync((UInt32)streamSize);

            string s = reader.ReadString(totalBytesRead);
            List<FoldersItemDisk> localData = ObjectSerializer<List<FoldersItemDisk>>.FromXml(s);
            section1FolderSelection.AssignCollectionToDataSource();
        }
    }
    catch (FileNotFoundException)
    {          

    }

}

Improved Generic Version

  1. This the "after" version.

  2. It makes USE OF GENERICS

Generics Version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//////////////////////////////////////////////////////////////////////
//
// Generic code
//
//
 
public delegate void ResetCollectionWithSource();
 
async private void SaveSearch_Click(object sender, RoutedEventArgs e)
{
    await SaveToDisk<FoldersItemDisk, FoldersItem>(MyGlobals.itemsFoldersItems, "SavedFolders.dat");
}
 
async private void OpenSearch_Click(object sender, RoutedEventArgs e)
{
    ResetCollectionWithSource resetCollectionWithSource = section1FolderSelection.AssignCollectionToDataSource;
    await ReadFromFile<FoldersItemDisk, FoldersItem>(MyGlobals.itemsFoldersItems, resetCollectionWithSource, "SavedFolders.dat");
}
 
private async Task ReadFromFile<T, T2>(ObservableCollection<T2> collection, ResetCollectionWithSource myfunc, string filename)
    where T : FoldersItemDisk, new() // I want to add objects that differ in properties, but the compiler gets upset
    where T2 : FoldersItem, new()    // Whatever types you add here must have "Folder” and “FolderName” properties from lines 43,44          
 
{
    try
    {
        StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
        StorageFile storageFile =
                await storageFolder.GetFileAsync(filename);

        using (IRandomAccessStreamWithContentType readStream = await storageFile.OpenReadAsync())
        using (DataReader reader = new DataReader(readStream))
        {
            ulong streamSize = readStream.Size;
            UInt32 totalBytesRead = await reader.LoadAsync((UInt32)streamSize);

            string s = reader.ReadString(totalBytesRead);
            List<T> localData = ObjectSerializer<List<T>>.FromXml(s);
            MyGlobals.itemsFoldersItems.Clear();
            foreach (T item in localData)
            {
                collection.Add(new T2 {
                        FolderName = item.FolderName,
                        Folder = await StorageFolder.GetFolderFromPathAsync(item.FolderName)
                });
            }
            myfunc();
                   
        }
    }
    catch (FileNotFoundException)
    {

    }

}
  • Why does type T2 have to have those 2 properties.

    Try this:

               foreach (T item in localData)

               {

                   collection.Add(new T2());

               }

  • ...or replace item.FolderName with

    System.IO.Path.GetDirectoryName(fileName)

  • It's 2014... Stop using XML. > ToXml Just serialize it to JSON.

    @Quintonn Because it just does. The point of the collection is to save it and I am using generics to save different object types. I just can't get around the fact that each object type has different properties. There is a way that is elegant somewhere in my brain but just can't get to it yet.
    @Phillip Good guidance. XML is wasteful and not that much more readable. The XML serializer/deserializer just worked so I kept it. I will look for a JSON version.
  •  

    JSON vs XML
    1
    2
    3
    4
    5
    6
    7
                string json = JsonConvert.SerializeObject(dataToSave, new JsonSerializerSettings
                {
                    Formatting = Formatting.Indented,
                });

                // Make xml out of it
                string localData = ObjectSerializer<List<FoldersItemDisk>>.ToXml(dataToSave);
Page 1 of 1 (4 items)
Leave a Comment
  • Please add 6 and 6 and type the answer here:
  • Post