|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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. BackgroundIn 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 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;
// ensure that the Image and the Stream will
// be closed when the PictureBox is diposed.
obj.Disposed += delegate(object sender, EventArgs e){
image.Dispose();
imgStream.Dispose();
};
}
}
// test code
ImageTools.SetImage(this.PictureBox1, "Image");
As long as you know the Type of But how do we need to write this in code? The first method avoids reflection. It uses the " 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;
// validate type of "owner"
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:
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:
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 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;
// ensure that the Image and the Stream will
// be closed when the PictureBox is diposed.
obj.Disposed += delegate(object sender, EventArgs e){
image.Dispose();
imgStream.Dispose();
};
}
}
The question is: How to get an instance of This is where DuckTyping comes into play. For classes which implement all interface-operations, you can create a DuckType by using the // test code
ImageTools.SetImage(
DuckTyping.Implement<IImageContainer>(this.PictureBox1), "Image");
ImageTools.SetImage(
DuckTyping.Implement<IImageContainer>(this.Label1), "Image2");
Implementation DetailsBefore starting, I would like to define the terms being used:
What happens when 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 namespace DynamicDucks.IImageContainer {
public class Duck0 : IImageContainer {
PicturBox _obj; // ducked object
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 SafetyYou 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. ReflectionAnother 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 CacheDuckTypes 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 Optimization OverloadsWith the There are two fields of possible optimization: Implementing an Array of Ducks with one callWith each call to // sample call with Arrays:
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 Static Specification of the DuckedTypeIf you call Consider the following code: public class MyLabel : Label {
public void SomeMethod() {}
}
The class 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 RestrictionsThe 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. ConclusionBy 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
|
||||||||||||||||||||||