Introduction
This article presents an implementation of runtime Duck-Typing for .NET.
Duck typing is a term for dynamic typing typical of some programming languages, such as Smalltalk, Visual FoxPro, or Coldfusion, where a variable's value itself determines what the variable can do.
Thus, an object having all the methods described in an interface can be made to implement that interface dynamically at runtime, even if the object�s type does not include the interface in its definition.
The term is a reference to the "duck test": "If it walks like a duck and quacks like a duck, it must be a duck." One can also say that the duck typing method ducks the issue of typing variables (from Wikipedia).
Note: Although the library can be used in all CLS compliant languages (C#, VB.NET, and many others), the article and the source code is based on C# 2.0.
Background
In statically typed languages like C#, the interface of a class defines which operations are supported. A supported operation may be calling a method with a specified signature, or getting or setting a property, or be able to attach to a specific event. In most cases, this makes perfect sense, since the compiler is able to verify if the operations are supported by the specified type.
But there are cases in which static typing is very annoying. Consider a method that loads an image into the Image
property of a PictureBox
:
public static class ImageTools {
public static void SetImage(PictureBox obj, string resourceName){
Stream imgStream =
typeof(ImageTools).Assembly.GetManifestResourceStream(resourceName);
Image image = Image.FromStream(imgStream);
obj.Image = image;
obj.Disposed += delegate(object sender, EventArgs e){
image.Dispose();
imgStream.Dispose();
};
}
}
ImageTools.SetImage(this.PictureBox1, "Image");
As long as you know the Type of obj
(PictureBox
), this code works well. But what if you want to call SetImage
with a Label
control? The Label
control contains both operations we need: the Image
property, and the Disposed
event, so it sould be possible to use a Label
.
But how do we need to write this in code? The first method avoids reflection. It uses the "as
" operator to check for each type that needs to be supported.
public static void SetImage(object owner, string resourceName){
if (owner == null) throw new ArgumentNullException("owner");
Component ownerAsComponent = owner as Component;
PictureBox ownerAsPictureBox = owner as PictureBox;
Label ownerAsLabel = owner as Label;
if (ownerAsPictureBox == null && ownerAsLabel == null)
throw new ArgumentException("Wrong owner Type! " +
"Only Labels and PictureBoxes are supported!");
Stream imgStream =
typeof(ImageTools).Assembly.GetManifestResourceStream(resourceName);
Image image = Image.FromStream(imgStream);
if (ownerAsPictureBox != null)
ownerAsPictureBox.Image = image;
if (ownerAsLabel != null)
ownerAsLabel.Image = image;
ownerAsComponent.Disposed += delegate(object sender, EventArgs e){
image.Dispose();
imgStream.Dispose();
};
}
This method has significant drawbacks:
- The
owner
parameter does not expose any Type-information that is expected by the function. You need to check them manually.
- Every type that has to be supported needs to be specified within the code. When you have to support a new Type, the assembly has to be recompiled.
- The code gets unnessersary long because of the nessersary type-checking.
Another possibility would be to use reflection. Most code that I've seen which uses a kind of Signature Based Polymorphism uses this approach:
public static void SetImage(object owner, string resourceName){
PropertyInfo piImage = owner.GetType().GetProperty("Image");
EventInfo eiDisposed = owner.GetType().GetEvent("Disposed");
if (piImage == null || piDisposed == null)
throw new ArgumentException("The 'owner' object" +
" is not compatible with the required interface!");
Stream imgStream =
typeof(ImageTools).Assembly.GetManifestResourceStream(resourceName);
Image image = Image.FromStream(imgStream);
piImage.SetValue(owner, image, new object[0]);
EventHandler disposedHandler = delegate(object sender, EventArgs e){
image.Dispose();
imgStream.Dispose();
};
eiDisposed.AddEventHandler(owner, disposedHandler);
}
By using Reflection, you can avoid having to specify every possible Type within the implementation, but then you have other drawbacks:
- The
owner
parameter do not expose any Type-information that is expected by the function. You need to check them manually.
- Reflection overhead degrades performance on each call.
- Reflection degrades maintainability (the method has to be implemented different).
- Some operations (like Indexers) are tricky to handle with Reflection.
So what can be done to solve the problem? Would it not be nice to declare the method like the following snippet?
public static void SetImage([any object with "Image" property
and "Disposed" event] obj, string resourceName){
obj.Image = ....;
}
The first thing which comes in mind of a .NET programmer is to declare an interface (I'll name it IImageContainer
) which contains the operations we need within the method. The SetImage
method will be modified to take an IImageContainer
instance instead of a PictureBox
instance:
public interface IImageContainer{
Image Image{set;}
event EventHander Disposed;
}
public static class ImageTools {
public static void SetImage(IImageContainer obj, string resourceName){
Stream imgStream =
typeof(ImageTools).Assembly.GetManifestResourceStream(resourceName);
Image image = Image.FromStream(imgStream);
obj.Image = image;
obj.Disposed += delegate(object sender, EventArgs e){
image.Dispose();
imgStream.Dispose();
};
}
}
The question is: How to get an instance of IImageContainer
which can be passed into the method? For a class whose source you can recompile, you can put the IImageContainer
interface within the class declaration, but it is impossible to implement the interface when you cannot recompile the class (the Label
or the PictureBox
classes, for example).
This is where DuckTyping comes into play. For classes which implement all interface-operations, you can create a DuckType by using the DuckTyping.Implement<IImageContainer>(object)
method. The method returns an instance of a dynamically constructed type that redirects all operations to the specified object.
ImageTools.SetImage(
DuckTyping.Implement<IImageContainer>(this.PictureBox1), "Image");
ImageTools.SetImage(
DuckTyping.Implement<IImageContainer>(this.Label1), "Image2");
Implementation Details
Before starting, I would like to define the terms being used:
- DuckedType: A DuckedType is the type for which a DuckType is generated (within the example, this would be the
PictureBox
or Label
types).
- DuckType: A DuckType is the Type which is generated at runtime, based on the Type of the interface, and the DuckedType.
- Duck: A Duck is an instance of a DuckType. Ducks are returned by the
Implement
method.
What happens when Implement
gets called? Basically a type (the DuckType) will be generated on-the-fly which takes an instance of the DuckedType within the constructor.
All interface-operations will be redirected to the DuckedType instance that was used to create the Duck. To show you how it works, the DuckType of our example (printed for the PictureBox
as the DuckedType) is shown below:
namespace DynamicDucks.IImageContainer {
public class Duck0 : IImageContainer {
PicturBox _obj;
public Duck0(PictureBox obj){
_obj = obj;
}
public Image Image {
set{
_obj.Image = value;
}
}
public event EventHandler Disposed {
add {_obj.Disposed += value;}
remove {_obj.Disposed -= value;}
}
}
}
Thread Safety
You can see that the DuckType itself holds no internal state - all operations are redirected to the ducked object. So thread safety is not affected by using DuckTyping.
Reflection
Another thing you may have noticed is: Reflection is only used for creating the DuckType, but not for calling the operations. Calling methods on Duck-Types can be made without the performance degradation from reflection.
DuckType Cache
DuckTypes are only created once for each interface and DuckedType. When a DuckType has been compiled successfully, it will be inserted into an internal cache-class. Subsequent calls to the Implement
method by using the same types will not compile the DuckType again, but will use the type from the cache.
Optimization Overloads
With the DuckTyping.Implement<TInterface>(obj)
method, you can create all DuckTypes you like. In some scenarios, you can optimize the way DuckTyping works by using alternative overloads. How significant the optimization (either in speed or in memory footprint) is, depends on the specific usage.
There are two fields of possible optimization:
Implementing an Array of Ducks with one call
With each call to DuckTyping.Implement<TInterface>(obj)
, an assembly will be emitted and compiled (when not found in the cache). When you know that there are several Ducks to be created, you can create them in one call. NDuck will emit a single source file for all DuckTypes that need to be created, so the generation and compilation process will happen only once. Because only one additional assembly will be loaded, the program also benefits from a smaller memory footprint.
IImageContainer[] ducks =
DuckTyping.Implement<IImageContainer>(this.PictureBox1, this.Label1);
IImageContainer pictureBoxDuck = ducks[0];
IImageContainer labelDuck = ducks[0];
ImageTools.SetImage(pictureBoxDuck, "Image");
ImageTools.SetImage(labelDuck, "Image2");
If you want to create serveral needed DuckTypes once, but you do not have an object-instance (e.g., at startup), you can use the DuckTyping.PrepareDuckTypes<TInterface>(params Type[] duckedTypes)
method.
Static Specification of the DuckedType
If you call DuckTyping.Implement<TInterface>(obj)
, the DuckType will be generated from the dynamic type of obj
(this is obj.GetType()
). In some scenarios, this could result in unnecessary DuckTypes being generated. By specifying the static object Type, you can force NDuck to use a static type instead of the dynamic type.
Consider the following code:
public class MyLabel : Label {
public void SomeMethod() {}
}
The class MyLabel
extents the default Label
class. If we let DuckTyping implement a Duck for the IImageContainer
interface (we assume that a DuckType for IImageContainer
based on the default Label
class has already been created and inserted into the cache), DuckTyping would create a new DuckType for the MyLabel
class. By specifying the TStaticObjectType
Type-parameter, the existing implementation can be used.
IImageContainer duck =
DuckTyping.Implement<IImageContainer, Label>(this.MyLabel1);
It would be possible to determine the best DuckedType for any type by reflecting the class hierarchy to get the deepest base-type that declares all needed members. This could be an improvement for the next version.
Requirements and Restrictions
The library is CLS compliant, it can be used by all CLS compliant languages such as C# or VB.NET. The language of your choice has to support Generics (it should be able to call generic methods).
NDuck emits code at runtime which will be compiled dynamically. Because the Compiler needz to access the Type of the interface and the Ducked-Type, these Types must have public scope. If the interface-type is not public, an exception will be thrown.
Because NDuck is implemented by using Generics, you have to know the Type of the interface at compile-time.
Conclusion
By using DuckTyping, you can combine type-safety with flexibility. DuckTyping is not something that you need in every scenario, but there are cases where DuckTyping is very useful. With DuckTyping, you can avoid type-casts (which are not any safer than DuckTyping) and redundant code.
History