There is a well-known advice (originating probably from the FDG book) to avoid interfaces with no members. Such interfaces are mostly used to mark types, and testing if a type is marked is done with the "is" operator like this:
It is being offered to use attributes instead, for example, like this:
The advantages of attributes are:
The disadvantages of attributes are:
A possible usage scenario for marker interfaces
I had one situation so far where marker interfaces seem to be quite useful and look neat. Frankly, as I write this, I realize that I could have taken attributes as well, but I already wrote too much of a post so it's a pity to throw it away. When I started this post, I was a strong believer that marker interfaces are good, now I think attributes deserve a chance as well :-)
Anyway, here's the example that I originally wanted to post (and you judge by yourself if marker interfaces are justified here). In the C# code editor I am building, language constructs were modeled by types inheriting from the Block class, for example, like this (a small subtree of the entire AST):
Now, some blocks are allowed to be nested in other blocks. For example, a class can be nested within a namespace or another class, and a method with a body can be nested in a class or a struct. To determine, where a language construct can be used, I introduced a set of marker interfaces:
Now, when we drag-and-drop or copy-paste blocks, determining if a block can be dropped within a container is easy. Each container has a list of allowed interfaces that can be accepted (not necessarily a single interface, we want to be flexible). Once we drag a block over the container, we look if the dragged block implements any of the interfaces we can accept:
We fill the AcceptableBlockTypes list like this:
And here's the definition for AddAcceptableBlockTypes:
Now I wonder how sane this is and if I should really have taken attributes instead. I like the usability of the current API and it looks like the approach works fine for my editor. Now let's wait and see how it scales as I modernize the editor to support more recent C# versions than 1.0 :) I'll keep you posted.
if (myObject is INamespaceLevel)
It is being offered to use attributes instead, for example, like this:
if (!obj.GetType().IsDefined(
typeof(ObsoleteAttribute), false))
...
The advantages of attributes are:
- they can have parameters
- you can precisely control how attributes are inherited. You can easily turn the attribute off on a derived type, whereas you can't erase an interface from a derived type's inheritance tree, if a base type already implements it.
The disadvantages of attributes are:
- the clumsy syntax
- and the runtime costs of checking (reflection is slower than the is operator).
A possible usage scenario for marker interfaces
I had one situation so far where marker interfaces seem to be quite useful and look neat. Frankly, as I write this, I realize that I could have taken attributes as well, but I already wrote too much of a post so it's a pity to throw it away. When I started this post, I was a strong believer that marker interfaces are good, now I think attributes deserve a chance as well :-)
Anyway, here's the example that I originally wanted to post (and you judge by yourself if marker interfaces are justified here). In the C# code editor I am building, language constructs were modeled by types inheriting from the Block class, for example, like this (a small subtree of the entire AST):
Now, some blocks are allowed to be nested in other blocks. For example, a class can be nested within a namespace or another class, and a method with a body can be nested in a class or a struct. To determine, where a language construct can be used, I introduced a set of marker interfaces:
public interface INamespaceLevel { }
public interface IClassLevel { }
public interface IMethodLevel { }
Now, when we drag-and-drop or copy-paste blocks, determining if a block can be dropped within a container is easy. Each container has a list of allowed interfaces that can be accepted (not necessarily a single interface, we want to be flexible). Once we drag a block over the container, we look if the dragged block implements any of the interfaces we can accept:
bool foundAssignable = false;
foreach (Type acceptableType in AcceptableBlockTypes)
{
if (acceptableType.IsAssignableFrom(dragged.GetType()))
{
foundAssignable = true;
}
}
We fill the AcceptableBlockTypes list like this:
AddAcceptableBlockTypes<IClassLevel>();
And here's the definition for AddAcceptableBlockTypes:
private Set<Type> AcceptableBlockTypes = new Set<Type>();
public void AddAcceptableBlockTypes(params Type[] acceptableBlockTypes)
{
foreach (Type t in acceptableBlockTypes)
{
if (!AcceptableBlockTypes.Contains(t))
AcceptableBlockTypes.Add(t);
}
}
public void AddAcceptableBlockTypes<T1>()
{
AddAcceptableBlockTypes(typeof(T1));
}
public void AddAcceptableBlockTypes<T1, T2>()
{
AddAcceptableBlockTypes(typeof(T1), typeof(T2));
}
public void AddAcceptableBlockTypes<T1, T2, T3>()
{
AddAcceptableBlockTypes(typeof(T1), typeof(T2), typeof(T3));
}
Now I wonder how sane this is and if I should really have taken attributes instead. I like the usability of the current API and it looks like the approach works fine for my editor. Now let's wait and see how it scales as I modernize the editor to support more recent C# versions than 1.0 :) I'll keep you posted.
No comments:
Post a Comment