patterncsharpModerate
Simple Generic output for Deserializer
Viewed 0 times
genericdeserializersimpleoutputfor
Problem
Lately I have been learning about serialization so I decided to write a little helper class for my application to make it easy to use this feature in multiple places. I actually mixed some generics in the code and want to hear your feedback on my overall approach.
SerializationService.cs
And here is how I use it in my code:
Serialization
De-serialization
SerializationService.cs
public class SerializationService
{
///
/// This method Serializes the given Obj into an XML file.
///
/// The obj to be serialized.
/// The file name with the full
/// path for the output serialized XML file.
public static void SerializeToFile(Object myObject, string fileFullPath)
{
var xmlDoc = new XmlDocument();
var xmlSerializer = new XmlSerializer(myObject.GetType());
using (var fs = new FileStream(fileFullPath, FileMode.Create))
{
var writer = new XmlTextWriter(fs, Encoding.Unicode);
xmlSerializer.Serialize(writer, myObject);
writer.Close();
}
}
///
/// This method deserializes the XML file into an Obj.
///
/// The XML file to read.
/// Deserialized obj of a type T.
public static T DeserializeToObj(string fuleFullPath)
{
var xmlSerializer = new XmlSerializer(typeof(T));
using (var reader = new FileStream(fuleFullPath, FileMode.Open))
{
return (T) xmlSerializer.Deserialize(reader);
}
}
}And here is how I use it in my code:
Serialization
SerializationService.SerializeToFile(
_partRepository.GetParts().Where(p => p.IsIgnored).ToList(),
fileFullPath);De-serialization
var ignoredParts = SerializationService.DeserializeToObj>(ignoredPartsXml);Solution
Here are a few remarks. I'm not perfect or write perfect code but I hope these tips will help write better code:
Naming consistency:
It would be easier to understand if the names of your two methods had opposing names. For example:
Also, if you're using the same type/kind of parameter or variable in one method, give it the same name in the other method.
Truly generic:
You should make your serialize method generic too:
In the point Type constraint a bit further you'll see why.
Scoping variables:
You should define variables as close to their usage as possible. In your case you can instantiate the
Checking for the file:
In both methods an exception can be thrown in certain situations. In the serialize method, if the path doesn't exist you'll get a
You should decide whether you want positive or negative evaluation on this. For example, only perform the method if the checks are positive or throw an argument exception if the checks are negative. That is up to you.
Also, like Heslacher stated: what if the file already exists? As for now, the content of the file will be overwritten by the new content.
Following:
can be replaced by:
Type constraint:
Remember my point for making the serialize method generic too? For now, you don't validate the incoming and outgoing object. What if I pass an instance of a class that has no public parameterless constructor? During runtime I'll get an
... cannot be serialized because it does not have a parameterless constructor.
The solution is to place type constraints on your methods. Definition from MSDN:
When you define a generic class, you can apply restrictions to the kinds of types that client code can use for type arguments when it instantiates your class, these restrictions are called constraints.
You should use the
-
class: the type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
-
new(): the type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.
Now you'll get a compile-time exception when trying to serialize an instance of a clas without a public parameterless constructor. You can read more on this here: Constraints on Type Parameters (C# Programming Guide).
Final code:
With all this applied, your code might look like following:
Naming consistency:
It would be easier to understand if the names of your two methods had opposing names. For example:
SerializeToFile and DeserializeFromFile. It's obvious that deserialization will return an object, omit that in the name.Also, if you're using the same type/kind of parameter or variable in one method, give it the same name in the other method.
Truly generic:
You should make your serialize method generic too:
public static void SerializeToFile(T obj, string fullPath)In the point Type constraint a bit further you'll see why.
Scoping variables:
You should define variables as close to their usage as possible. In your case you can instantiate the
XmlSerializer class inside the using statement since you don't need it outside the statement. Example:using (var stream = new FileStream(fullPath, FileMode.Open))
{
var xmlSerializer = new XmlSerializer(typeof(T));
}Checking for the file:
In both methods an exception can be thrown in certain situations. In the serialize method, if the path doesn't exist you'll get a
DirectoryNotFoundException and in the deserialize method you'll get a FileNotFoundException if the file doesn't exist. This can easily be solved://Check for directory in serialize:
var directoryPath = Path.GetDirectory(fileFullPath);
var directoryExists = Directory.Exists(directoryPath);
//Check for file in deserialize
var fileExists = File.Exists(fileFullPath);You should decide whether you want positive or negative evaluation on this. For example, only perform the method if the checks are positive or throw an argument exception if the checks are negative. That is up to you.
Also, like Heslacher stated: what if the file already exists? As for now, the content of the file will be overwritten by the new content.
using statement:Following:
using (var fs = new FileStream(fileFullPath, FileMode.Create))
{
var writer = new XmlTextWriter(fs, Encoding.Unicode);
xmlSerializer.Serialize(writer, myObject);
writer.Close();
}can be replaced by:
using (var stream = new FileStream(fullPath, FileMode.Create))
using (var writer = new XmlTextWriter(stream, Encoding.Unicode))
{
var xmlSerializer = new XmlSerializer(obj.GetType());
xmlSerializer.Serialize(writer, obj);
}Type constraint:
Remember my point for making the serialize method generic too? For now, you don't validate the incoming and outgoing object. What if I pass an instance of a class that has no public parameterless constructor? During runtime I'll get an
InvalidOperationException with a message like:... cannot be serialized because it does not have a parameterless constructor.
The solution is to place type constraints on your methods. Definition from MSDN:
When you define a generic class, you can apply restrictions to the kinds of types that client code can use for type arguments when it instantiates your class, these restrictions are called constraints.
You should use the
class and new() type constraints. Here's their definition:-
class: the type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
-
new(): the type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.
Now you'll get a compile-time exception when trying to serialize an instance of a clas without a public parameterless constructor. You can read more on this here: Constraints on Type Parameters (C# Programming Guide).
public static void SerializeToFile(T obj, string fullPath) where T : class, new()
{
//method body
}
public static T DeserializeFromFile(string fullPath) where T : class, new()
{
//method body
}Final code:
With all this applied, your code might look like following:
public class SerializationService
{
public static void SerializeToFile(T obj, string fullPath) where T : class, new()
{
if(Directory.Exists(Path.GetDirectoryName(fullPath)))
{
using (var stream = new FileStream(fullPath, FileMode.Create))
using (var writer = new XmlTextWriter(stream, Encoding.Unicode))
{
var xmlSerializer = new XmlSerializer(obj.GetType());
xmlSerializer.Serialize(writer, obj);
}
}
}
public static T DeserializeFromFile(string fullPath) where T : class, new()
{
if(File.Exists(fullPath))
{
using (var stream = new FileStream(fullPath, FileMode.Open))
{
var xmlSerializer = new XmlSerializer(typeof(T));
return (T) xmlSerializer.Deserialize(stream);
}
}
return default(T);
}
}Code Snippets
public static void SerializeToFile<T>(T obj, string fullPath)using (var stream = new FileStream(fullPath, FileMode.Open))
{
var xmlSerializer = new XmlSerializer(typeof(T));
}//Check for directory in serialize:
var directoryPath = Path.GetDirectory(fileFullPath);
var directoryExists = Directory.Exists(directoryPath);
//Check for file in deserialize
var fileExists = File.Exists(fileFullPath);using (var fs = new FileStream(fileFullPath, FileMode.Create))
{
var writer = new XmlTextWriter(fs, Encoding.Unicode);
xmlSerializer.Serialize(writer, myObject);
writer.Close();
}using (var stream = new FileStream(fullPath, FileMode.Create))
using (var writer = new XmlTextWriter(stream, Encoding.Unicode))
{
var xmlSerializer = new XmlSerializer(obj.GetType());
xmlSerializer.Serialize(writer, obj);
}Context
StackExchange Code Review Q#78191, answer score: 11
Revisions (0)
No revisions yet.