Tuesday, March 3, 2009

Serialization

There are several ways to implement serialization in .NET. The simplest way is to mark the class as Serializable and then to create a formatter to do the serialization. Fields that are not serializable can be marked as such and will not be serialized.
[Serializable]
public class SomeClass {
//mark members that are not serializable
[NonSerialized] SomeNoneSerializedType t;
}
The formatter can be created in any class that needs to serialize the above class.
public void SomeSaveHandler(Object sender, EventArgs e) {

using (SaveFileDialog dlg = new SaveFileDialog())
{
if (dlg.ShowDialog() != DialogResult.OK) return;
using (Stream stream =
new FileStream(dlg.FileName, FileMode.Create, FileAccess.Write))
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, instanceSomeClass);
}
}
}
Deserialization is similar.
public void SomeOpenHandler(Object sender, EventArgs e) {

using (OpenFileDialog dlg = new OpenFileDialog())
{
if (dlg.ShowDialog() != DialogResult.OK) return;
using (Stream stream =
new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read))
{
IFormatter formatter = new BinaryFormatter();
SomeClass someClass = (SomeClass) formatter.Deserialize(stream);
}
}
}
More than one object can be serialized by having additional calls to serialize; one for each object. During deserialization, make a corresponding call to deserialize for each object that was serialized; use the same order when deserializing as was used when serializing.

For elements that are calculated fields, they can be recalculated after the object has been deserialized, by implementing the IDeserializationCallback interface. Define the callback method OnDeserialization to instantiate any calculated fields. The parameter to this method is not implemented at this time, so it is always null.
[Serializable]
public class SomeClass: IDerializationCallback {
//mark members that are not serializable
[NonSerialized] SomeNoneSerializedType t;

public void OnDeserialization(object sender) {
t = SomeCalculation();
}
}


The other method to serialize takes a different approach. Instead of assuming that everything is serializable, it assumes that nothing is serializable. Instead of indicating what should not be serialized, the class must specifically serialize each element. This technique is implemented in the interface ISerializable. Define the method GetObjectData and define a constructor that will be used during deserialization.
[Serializable]
public class SomeClass : ISerializable {
SomeType t;
SomeNoneSerializedType non;

public SomeClass(
SerializationInfo info, StreamingContext context)
{
t = (SomeType) info.GetValue("myType", typeof(SomeType));
non = SomeCalculation();
}

public void GetObjectData(
SerializationInfo info, StreamingContext context)
{
info.AddValue("myType", t);
}
}
Call info.AddValue for every member that needs to be serialized. Call info.GetValue for every member that needs to be deserialized.

There is no need for the deserialization callback interface, since calculated fields can be instantiated in the deserialization constructor.

This technique is useful for classes that extend a non-serializable class. The first technique will fail, if the base class cannot be serialized. By using the second technique and specifically serializing each element, the base class does not need to be serializable. The trick in this case is to make sure that any other constructors also get called when the deserialization constructor is called.

For the class that has to serialize this class, there is no difference when creating a formatter for either of these techniques. The calling class just calls serialize and deserialize on the formatter, it is up to the serializable class to determine how it will serialize.



It is possible to add methods to a class that will serialize/deserialize the class by creating a formatter. This encapsulates the entire process of serializing the class so that the class that is doing the serialization will not have to implement the details for creating the formatter.

The second technique is the preferred technique for implementing such methods. While the first technique could be used, its simplicity is lost, since it is not possible to deserialize into this. In order to make the first technique work from a method in the class, it would be necessary to deserialize into another class and then copy member by member into the current class. Another possible implementation would be to serialize/deserialize member by member.

Sunday, March 1, 2009

Handling Arrows

The arrow keys for a container control in .NET are used to switch the focus amongst multiple controls in the container. If a container has several controls, then the arrow keys will move the focus from one control to the next. This is a special behavior for the arrow keys in a container control. In the default case, the arrow keys never reach the controls in the container. There are several ways to get the arrow keys to the controls themselves.

The first way is to allow the arrow keys to maintain the special function that they have. Override the ProcessCmdKey method and add the additional handling for the arrow keys. To allow the keys to have the special function, be sure to call the base class method, in order to have the special function executed.

The second technique is to remove the special function altogether. There is another method called IsInputKey. If it returns true, then the key is passed directly to the control, without the preprocessing of the key. If it returns false, then preprocessing is executed; the key will only make it to the control if the preprocessing routes it there. If the key makes it to the control, then it can be caught in the normal KeyDown handler.

There is a third way, which I will call technique one-and-a-half. If in technique one, the ProcessCmdKey method returns true instead of returning the base class method, then the preprocessing will not be done. This has the same effect as method two. Instead of handling the key in KeyDown, handle the key in ProcessCmdKey.

There is another method for solving this problem: override OnPreviewKeyDown. This method can behave like ProcessCmdKey, but it works for all the keys: extra coding can be done for any key. There is even an event args property for IsInputKey which allows this method to perform like IsInputKey. To remove the command key functionality, set IsInputKey to true.

KeyCode is the code for the key pressed, without any modifiers like Shift or Control.

KeyData is the code for the key OR-ed with any modifier keys, like Shift or Control.

KeyValue is the integer equivalent of KeyData for passing to unmanaged methods.

KeyChar is generated from KeyPress event and has the ASCII code for a key.

Followers