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
I wanted to take some code and make it more general purpose
C# generics didn’t quite cut it.
I wanted a generic routine that could save objects with different properties
But I didn’t want ugly, “hard to comprehend in 3 months from now” code
The traditional approach is to use Interfaces to guarantee that templated objects support the same methods
But I wanted to make objects generic that had different properties (An impossibility as far as I could tell)
If you want to support objects that have different properties, I couldn’t find an elegant way to do it
- See lines 42 to 45 of part 2 of the code
- T2 needs to have a property called FolderName and Folder
- So unless my template objects have the same properties, I am out of luck
- I can see why the compiler could complain
- But I am will to sacrifice compile time safety in favor of code re-use
The code speaks for itself so there is barely any narrative here
I present the before and after
I’m sure many of you will find a better way and I look forward to seeing the comments
There is use of delegates so that even a function call can be abstracted out of the code
Some of code can be found here for serialization
The code can serialize and save a collection of objects
The collection is composed of string paths to folders (I couldn’t save the StorageFolder object by itself)
Non-Generic Version
This the "before" version.
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
This the "after" version.
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);