Click here to Skip to main content
Click here to Skip to main content

C# Proposal: Compile Time Static Checking Of Dynamic Objects

By , 15 Apr 2010
 
free hit counters

C# 4.0 introduces a new type: dynamic. dynamic is a static type that bypasses static type checking.

This new type comes in very handy to work with:

Because static type checking is bypassed, this:

dynamic dynamicValue = GetValue();
dynamicValue.Method();

is equivalent to this:

object objectValue = GetValue();
objectValue
    .GetType()
        .InvokeMember(
            "Method",
            BindingFlags.InvokeMethod,
            null,
            objectValue,
            null);

Apart from caching the call site behind the scenes and some dynamic resolution, dynamic only looks better. Any typing error will only be caught at run time.

In fact, if I'm writing the code, I know the contract of what I'm calling. Wouldn't it be nice to have the compiler do some static type checking on the interactions with these dynamic objects?

Imagine that the dynamic object that I'm retrieving from the GetValue method, besides the parameterless method Method also has a string read-only Property property. This means that, from the point of view of the code I'm writing, the contract that the dynamic object returned by GetValue implements is:

string Property { get; }
void Method();

Since it’s a well defined contract, I could write an interface to represent it:

interface IValue
{
    string Property { get; }
    void Method();
}

If dynamic allowed to specify the contract in the form of dynamic(contract), I could write this:

dynamic(IValue) dynamicValue = GetValue();
dynamicValue.Method();

This doesn't mean that the value returned by GetValue has to implement the IValue interface. It just enables the compiler to verify that dynamicValue.Method() is a valid use of dynamicValue and dynamicValue.OtherMethod() isn't.

If the IValue interface already existed for any other reason, this would be fine. But having a type added to an assembly just for compile time usage doesn't seem right. So, dynamic could be another type construct. Something like this:

dynamic DValue
{
    string Property { get; }
    void Method();
}

The code could now be written like this:

DValue dynamicValue = GetValue();
dynamicValue.Method();

The compiler would never generate any IL or metadata for this new type construct. It would only be used, at compile time, for static checking of dynamic objects. As a consequence, it makes no sense to have public accessibility, so it would not be allowed.

Once again, if the IValue interface (or any other type definition) already exists, it can be used in the dynamic type definition:

dynamic DValue : IValue, IEnumerable, SomeClass
{
    string Property { get; }
    void Method();
}

Another added benefit would be IntelliSense.

I've been getting mixed reactions to this proposal. What do you think? Would this be useful?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Paulo Morgado
Software Developer (Senior) Paulo Morgado
Portugal Portugal
Member

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralGomemberQwertie21 Apr '10 - 4:59 
This reminds me of Google Go's interfaces. In Go, classes do not have to explicitly say what interfaces they implement, and anybody that notices a pattern in the methods of two otherwise unrelated classes can declare that they both implement a certain interface.
 
Go's feature, unlike what is proposed here, provides compile-time type checking, and since Go is built heavily around these "implicit" interfaces, I assume Google found a way to give them high performance similar to .NET interfaces. As such I think Microsoft would be smart to consider adding support for Go-style interfaces, which in turn would allow a fast implementation of the Go language on the CLR.
 
Go-style interfaces would allow developers to work around limitations in the way Microsoft designed interfaces. For instance, there are a lot of read-only collection classes (or collections like Stack where modifications are constrained), but Microsoft didn't bother to make a IReadOnlyCollection interface, so we're stuck writing four unsupported methods (Add, Clear, Remove, CopyTo) in addition to the three that matter (GetEnumerator, Count, Contains).
 
And that reminds me, interfaces should be able to have default implementations. For instance, Contains(T) could have a default implementation like this:
 
bool Contains(T item) {
    var c = EqualityComparer<T>.Default;
    foreach(T t in this)
        if (item.Equals(t))
            return true;
    return false;
}
 
And while I'm here, I'd like to plug my most-wanted .NET feature, covariant return types.
GeneralRe: GomemberPaulo Morgado23 Apr '10 - 2:12 
Now that we have generic interface and generic delegate variance, it's a lot easier to implement return type variance.
 
The reason you can't have return value type covariance is because, although the return types aren't taken in account for overload resolution in C# or VB, the CLR allows the definition of methods with the same parameter signature and different return types. So, return value type covariance is not possible in the CLR, which makes it impossible to C# (or VB).
 
Default implementation is also a big request from C# users. But given that a class can implicitly or explicitly implement an interface, how would you declare it? One way to do it would be to have these default implementations always be explicit implementations and only access members of that same interface.
 
As for duck typing, for me, the safest way to implement it is with dynamic binding. And that’s why I would like to have static type checking for dynamic.
Cheers,

Paulo Morgado

Portugal - Europe's West Coast

GeneralRe: GomemberQwertie23 Apr '10 - 4:24 
Even with this almost never used feature (that I've never heard of) of having overloaded methods differing only in return value, it's still not a reason not to support return type covariance. Consider:
class A {
   public virtual A F();
}
class B : A {
   public override B F();
}
Obviously F() returning B overrides F() returning A. There is no possible ambiguity. Now consider:
class A {
   public virtual A F();
   public virtual B F();
}
class B : A {
   public override C F();
}
class C : B {}
Even here, there is clearly just one "best" interpretation: B.F should override the version of A.F that returns a B. So what's the problem? Even if you can find an ambiguous case, the CLR is famous for its attribute system; it could offer an attribute to resolve the ambiguity.
GeneralRe: GomemberPaulo Morgado23 Apr '10 - 8:09 
With C# 4.0, you can do this:
 
interface I<out T>
{
    T F();
}
 
class A : I<A>
{
    public virtual A F() { return new A(); }
}
 
class B : A, I<B>
{
    public virtual B F() { return new B(); }
}
 
and this:
 
var a = new B();
Console.WriteLine(a.GetType());
Console.WriteLine(a.F().GetType());
 
You are only looking at the return type. If your class had a method like this:
 
public virtual A Convert(A from) { ... }
 
Would you also expect your type to be both covariant on the output value and contravariant on the input value? You would have to choose just one variance type.
 
I'm not sure with there's only generic interface and generic delegate variance, but it's better than nothing. Smile | :)
 
You cannot write that code (the second snippet) in C# although I think you could write it in IL, but I haven't done it myself.
 
Suppose
Cheers,

Paulo Morgado

Portugal - Europe's West Coast

GeneralRe: GomemberQwertie23 Apr '10 - 16:07 
I don't think the fact that A and B implement two (separate!) interfaces has any use. If you change the example to
A a = new B();
Console.WriteLine(a.F().GetType());
... I'm pretty sure it'll print "A". What you have done is not an override, so it's pretty useless. I'm not aware of any changes to C# 4.0 with regard to return type covariance, so I assume the only workaround is the one I described in my blog.
Would you also expect your type to be both covariant on the output value and contravariant on the input value?

Of course. C++ supports it. Why not C#?
You would have to choose just one variance type.

I don't see why. For type safety, it is natural for return values and "out" parameters to be covariant; it is natural for input parameters to be contravariant.
GeneralRe: GomemberPaulo Morgado25 Apr '10 - 11:56 
Yes it will print "A". Variance is only for generic interfaces and delegates.
 
Because object cloning is a deprecated concept, there's no generic ICloneable<T> interface. If there was, it would be covariant in T on .NET 4.0 and all the trouble you mention on your blog would be a thing of the past - and it still can be if you define your own ICloneable<out T> interface.
 
C++ is neither strongly typed or type safe like C# is. And I for one don't want C# to change in that respect.
Cheers,

Paulo Morgado

Portugal - Europe's West Coast

GeneralRe: GomemberQwertie28 Apr '10 - 18:10 
Oh noes! Return type covariance is weak typed and type unsafe! Fine, shut off your brain. Cloning is one of the least useful uses of return type covariance, I just used it because it's the simplest example.
GeneralRe: GomemberPaulo Morgado28 Apr '10 - 21:53 
I said that C++ is weakly typed and unsafe, not variance.
 
Because cloning is considered a deprecated concept, there's no IClonable<out T>.
Cheers,

Paulo Morgado

Portugal - Europe's West Coast

GeneralWish-list item: narrowly-scoped extension types/functionsmembersupercat915 Apr '10 - 5:38 
If one is discussing wish-list items for future language extensions, how about the ability to define extension types? An type X which extends type or interface Y would be generate code for type (or interface) Y, but allow type X's extension methods to be applied to objects thereof. An object of type Y could be typecast or type-coerced into type X without need for any run-time code (since they would in fact be the same type). Note that even non-inheritable types could be extended, and any methods or properties of the extending type would always shadow those of the extended type but never override them.
 
Note that unlike the existing means of applying extension methods, the scope of extension types would be well-defined. The methods associated with the extension types would be applied when, and only when, objects were cast or coerced into the extension types.
 
On a related note, I'd also like to see the ability for classes to define non-overridable methods for themselves, which would accept a 'self' parameter and could be invoked using normal method syntax even when an object happened to be Nothing. The intended typical use for such methods would be to behave logically if the object was nothing, and call an overridable method if not. For example, one could define Thingie.Zap as a method which would simply return if Thingie was Nothing, but call Thingie.Dispose otherwise. In practice, I use a global method Zap(x as iDisposable), but an Intellisense-friendly format might be nicer.
GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmemberPaulo Morgado15 Apr '10 - 12:47 

Can you provide code samples for what you are proposing as extension types?

As for your Zap method, although valid for the compiler, it is not recommended to use extension methods on null instances because the same method might be added on a future version of the extended type and it might break recompiled code. Something like String.IsNullOrEmpty should never e an extension method - although I've advocated that myself in the past.

Paulo Morgado
Portugal - Europe's West Coast

GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmembersupercat916 Apr '10 - 6:08 
Paulo Morgado wrote:
Can you provide code samples for what you are proposing as extension types?

 
In VB-style, something like:
Extension xStringBuilder
  Extends System.Text.StringBuilder
 
  Public Property Value() As String
    Get
      Return Me.ToString
    End Get
    Set(ByVal Value as String)
      Me.Length = 0
      Me.Append(Value)
    End Set
  End Property
 
  Public Sub Clear
    Me.Length=0
  End Sub
End Extension
 
Extension xDisposable
  Extends iDisposable
 
  Public Shared Sub Dispose(ByVal Shared Self As xDisposable)
    ' Note the following cast will always be valid, and the compiler
    ' will know this; no need for a run-time check.
    If Self isNot Nothing then Ctype(Self,iDisposable).Dispose
  End Sub
End Extension
 
Extension xZap(of T)
  Extends T
 
  Public Shared Sub Dispose(ByVal Shared Self as xZap(of T))
    If Self IsNot Nothing Then
      Dim Thing As iDisposable = TryCast(Self, iDisposable)
      If Thing IsNot Nothing Then Thing.Dispose
    End Thing
  End Sub
End Extension
A program could use xStringBuilder anyplace it would otherwise use System.Text.StringBuilder, xDisposable anyplace it would use iDisposable, and xZap(of someType) anyplace it would use someType. Any time the types were referred to as xStringBuilder, xDisposable, or xZap, the extension methods would be used. Otherwise they would not be.
Paulo Morgado wrote:
As for your Zap method, although valid for the compiler, it is not recommended to use extension methods on null instances because the same method might be added on a future version of the extended type and it might break recompiled code.

With the existing extension-method semantics, if a type that's extended is changed there's a danger of breaking existing code whether or not one uses null instances. For example, if one extended Graphics with a DrawParallelogram function that took three vertices and drew a parallelogram whose unspecified vertex was opposite the first, and such a function was later added to the Graphics type but the unspecified vertex was opposite the second, code that expected the former behavior would break. If the class were extended via my proposed syntax, the code would not break (since the extension method would always shadow the base-class method for objects declared to be of the extension type).
GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmemberPaulo Morgado16 Apr '10 - 14:40 

So, if I understand you correctly, what you are in fact proposing is some type of runtime subtyping of instances.

This would be true: typeof(StringBuilder).IsAssignableFrom(typeof(xStringBuilder))

And there would always be an explicit cast between StringBuilder and xStringBuilder.

Is that it?

Apart the fact that there aren’t extension properties (now), what do you get out of it that you don’ get out of extension methods?

Cheers,
Paulo Morgado
Portugal - Europe's West Coast

GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmembersupercat917 Apr '10 - 6:23 
Paulo Morgado wrote:
So, if I understand you correctly, what you are in fact proposing is some type of runtime subtyping of instances.

 
My "extensions" would be entirely a compile-time construct. A statement "Dim SB as new xStringBuilder" would create a new object of type System.Text.StringBuilder, but any time the object was used with a declared type of xStringBuilder, the applied extensions would be available to it (and would shadow any existing properties or methods the class might have).
 
With the existing extension-method syntax, if object G is of type System.Drawing.Graphics, there is no clear way for the programmer to indicate whether G.DrawParallelogram should invoke System.Drawing.Graphics.DrawParallelogram, or whether it should invoke an extension method. Under my proposed syntax, if G is declared to be of type System.Drawing.Graphics, then DrawParallelogram would invoke System.Drawing.Graphics.DrawParallelogram if it exists, and refuse to compile if it does not. If G is declared to be of type xGraphics, which extends System.Drawing.Graphics with a DrawParallelogram method, then G.DrawParallelogram(br,a,b,c) would be translated to xGraphics.DrawParallelogram(G,br,a,b,c). Likewise, if SB was of type xStringBuilder, the statement SB.Value = "HEY" would translate to xStringBuilder.Value__Set(SB, "HEY"). In any case, G and SB would actually be types of System.Drawing.Graphics and System.Text.StringBuilder even though they were declared as being extension types.
GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmemberPaulo Morgado17 Apr '10 - 10:45 
Extension methods are normal static methods where the first parameter is the extended type. You can always call that explicitly (that's what the compiler does), if you want to.
 
I don't really see how your proposal can gain traction.
Cheers,

Paulo Morgado

Portugal - Europe's West Coast

GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmembersupercat919 Apr '10 - 5:10 
Paulo Morgado wrote:
Extension methods are normal static methods where the first parameter is the extended type. You can always call that explicitly (that's what the compiler does), if you want to.

 
If one calls an extension method explicitly, then is behaves just like any other function and the fact that it's declared as an extension method is irrelevant.
 
Extension methods are designed to be a convenient shortcut by which static methods with a particular signature may be called as though they are methods of a class. Unfortunately, the scoping and priority rules used by the present implementation have some very undesirable features. Problems will arise if one wishes to have different parts of a project extend a class with different extensions that have conflicting method names. Problems will also arise if a future version of a base class adds method names that conflict with the names of extension methods. My implementation would avoid both of those problems. I would think that would be a significant advantage.
 
Otherwise, my preferred "implementations" for extension methods would be to either have an Intellisense substitution so that typing SomeObject.ExtensionMethod( would be converted to ExtensionModule.ExtensionMethod(SomeObject, in the editor (defining the Extension method would add it to the pop-up list that appears when typing SomeObject., thus allowing a handy means of selecting it, or else use the syntax Object..Method() to invoke extension methods (the latter always invoking the extension method even if Object supports Method natively).
GeneralRe: Wish-list item: narrowly-scoped extension types/functionsmemberPaulo Morgado19 Apr '10 - 13:04 
I see.
 
I've never run into a situation I couln't handle in terms of the rght exension to use, but I do feel that the day will come. Smile | :)
Cheers,

Paulo Morgado

Portugal - Europe's West Coast

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 15 Apr 2010
Article Copyright 2010 by Paulo Morgado
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid