Share via


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

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. https://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()     where T2 : FoldersItem, new()                 {     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)     {     } }

Comments

  • Anonymous
    July 14, 2014
    Why does type T2 have to have those 2 properties. Try this:            foreach (T item in localData)            {                collection.Add(new T2());            }

  • Anonymous
    July 14, 2014
    ...or replace item.FolderName with System.IO.Path.GetDirectoryName(fileName)

  • Anonymous
    July 14, 2014
    It's 2014... Stop using XML. > ToXml Just serialize it to JSON. @QuintonnBecause 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.@PhillipGood 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.

  • Anonymous
    July 15, 2014
      JSON vs XML1
    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);