The syntax of StaDyn is based on C# 3.0. It can be specified the type of references
explicitly or use the var
keyword as implicitly typed references.
However, var
references
of StaDyn are more powerful than implicitly typed local variables in C# 3.0, since var
is
a new type in StaDyn. It can be used for parameter types, return values, class fields and
initialized local variables.
StaDyn also supports dynamic
references, later added to C# 4.0. Its behavior is similar
to var
, but with a more lenient approach to follow the flavor of dynamic languages.
Later, we detail the exact difference between var
and dynamic
.
Static typing commonly forces a variable of type T to have the same type within the scope in
which it is defined. However, this is not the case in dynamic languages,
where variables can hold different types in the same scope. StaDyn allows
variables to hold different types in the same scope, but static type checking
is still performed, holding the concrete types of each var
and dynamic
reference.
using
System;class
Test {public static void
Main() { Console.Write("Your age, please: "
);var
age = Console.In.ReadLine(); Console.WriteLine("You are "
+ age +" years old."
); age = Convert.ToInt32(age); Console.WriteLine(age.GetType()); age++; Console.WriteLine("Happy birthday, you are "
+ age +" years old now."
);int
length = age.Length;// * Compiler error
} }
The age
variable is first inferred as string
. Then it changes to int
.
In each usage of age
, the particular concrete type is used to type-check the program.
In this way, the compiler shows an error for the last line of code,
since integers do not provide the Length
property.
The generated code does not use a single Object
variable to represent age
, but
two different variables whose types are string
and int
. This makes the generated code
to be more efficient, since runtime type conversions are not required.
The static type system of StaDyn is flow-sensitive. This means that it takes
into account the flow context of each var
and dynamic
reference.
It gathers concrete type information (opposite to classic abstract type systems):
StaDyn knows all the possible types of a var
/ dynamic
reference may have.
Following this scheme, the language offers statically typed duck typing.
If a var
reference points to a set of objects and all of them implement
the m
message (i.e., method, property or field), it can be safely invoked.
The objects do not need to implement a common interface or class declaring
the implementation of m
. The following code is an example of this feature:
var
exception;if
(new
Random().NextDouble()<0.5) exception =new
ApplicationException("An application exception."
);else
exception =new
SystemException("A system exception"
); Console.WriteLine(exception.Message);
In the code above, exception
may point to anApplicationException
or
to a SystemException
object. Both objects have the Message
property and,
therefore, it is safe to use this property. It is not necessary
to define a common interface or class to pass this message.
Since type inference system is flow-sensitive and uses concrete types,
the programmer obtains a type-safe duck-typing system.
If dynamic
is used to declare exception
, type checking is a little bit
different. In such a case, an m
message can be passed if either
ApplicationException
or SystemException
provides an
implementation for m
(a compiler error is shown if neither provides m
).
This behavior is more lenient and closer to dynamic language,
but still provides static type checking.
Let's add another alternative in the assignment of the exception
reference:
var
exception; Random random = new Random();switch
(random.Next(1,4)) {case
1: exception =new
ApplicationException("An application exception."
);break
;case
2: exception =new
SystemException("A system exception"
);break
;case
3: exception ="This is not an exception"
;break
; } Console.WriteLine(exception.ToString()); Console.WriteLine(exception.Message);// * Compiler error?
The ToString
message is correct because it is offered by the three
possible objects created. However, the Message
property is not provided
by string
. By default, the compiler uses the everythingStatic
option, and the following error message is shown:
C:\...> sample3.cs(21.37): Error ErrorManagement.UnknownMemberError (Semantic error). 'Message': no suitable member found.
However, we can be more optimistic and set all the var
references in
the program as dynamic
. In this case, the compiler accepts a
message if there is one possibility that the program can behave
without runtime type errors. The compiler accepts the previous
program if we compile it with the everythingDynamic option:
C:\...> stadyn /ed sample3.cs
We do not need to set all the var
references in the program as dynamic.
First, we can declare the exception
reference as dynamic
.
It is also possible to specify the dynamism of a var
reference
with an XML file. The following is the sample3.dyn
file
that sets the exception reference as dynamic:
<?xml
version="1.0"
encoding="utf-8"
?> <application
name="sample3"
> <namespace
name="GettingStarted"
> <class
name="Test"
> <method
name="Main"
> <dynvar
name="exception"
/> </method
> </class
> </namespace
> </application
>
The existing StaDyn plugin for Visual Studio, among other functionalities,
handles the XML files used to modify the dynamism of var
references.
It is worth noticing that, even though the everythingDynamic option makes the compiler to be more lenient, static typing is still performed. This is shown in the following example:
var
reference;if
(new
Random().NextDouble() < 0.5) reference ="String"
;else
reference = 3; Console.WriteLine(reference.Message);
The error message obtained when compiled in dynamic mode is the following one:
C:\...> stadyn /ed sample3.cs C:\...> sample4.cs(13.41): Error ErrorManagement.NoTypeHasMember (Semantic error). The dynamic type '\/([Var(6)={6}=string] ,[Var(5)={5}=int])' has no valid type type with 'Message' member.
Concrete type reconstruction is not limited to local variables.
StaDyn performs a global flow-sensitive analysis of var
and dynamic
references.
The result is a powerful parametric polymorphism (generics) more
straightforward for programmers than the one offered by Java,
C# and C++. For example, we can create the
following generic Wrapper
class:
class
Wrapper {private var
attribute;public
Wrapper(var
attribute) {this
.attribute = attribute; }public var
get() {return
attribute; }public void
set(var
attribute) {this
.attribute = attribute; } }class
Test {public static void
Main() {string
aString;int
aInt; Wrapper wrapper =new
Wrapper("Hello"
); aString = wrapper.get(); aInt = wrapper.get();// * Compiler error
wrapper.set(3); aString = wrapper.get();// * Compiler error
aInt = wrapper.get(); } }
The Wrapper
class can wrap any type. Each time we call the set
method,
the new concrete type of the parameter is saved in the type of
the implicit object (i.e., the object used to call set
).
By using this mechanism, the two lines indicated in the
code above report compilation errors. Notice that this
coding style is type safe and it is easier to learn
than the parametric polymorphism used in C++ and the
bounded polymorphism offered by Java and C#.
The runtime performance of the code above is the same
as if we had specified types explicitly. Although the
source code makes use of var
references, concrete types are
known at compile time. The generated code uses that static
type information to undertake different optimizations and
provide better runtime performance than existing dynamic languages.
Var
and dynamic
parametersSince the StaDyn compiler type-checks both dynamically and statically typed code, it is possible to combine both approaches in the very same application. Let's analyze the following code:
public static var
upper(var
parameter) {return
parameter.ToUpper(); }public static var
getString(var
parameter) {return
parameter.ToString(); }
Both methods require the parameter to implement a method, returning the result
of the method call. In the second case, the method will be valid for any
argument, since ToString
is provided by every object. In the upper
method,
the parameter should be any object able to respond to the ToUpper
message.
To support var
and dynamic
parameters, StaDyn implements a constraint-based
type system. The next code is correct:
string
aString ="hello"
; aString=upper("hello"
); Console.WriteLine(aString);
In this case, the returned type is inferred to string. What happens if the parameter has a flow-sensitive type? Here we have an example:
var
reference;if
(new
Random().NextDouble() < 0.5) reference ="hello"
;else
reference =new
SystemException("A system exception"
); aString=getString(reference);// * Correct!
Console.WriteLine(aString); aString=upper(reference);// * Compiler error (reference is static)
reference
may point to a string or to an exception.
Therefore, it is type safe to invoke the getString
method,
but we might get an erroneous dynamic behavior if we call
the upper
method. This is the reason why, if we set
reference as static we get a compiler error.
Otherwise, if reference is set as dynamic (or declared as dynamic
),
the same source code will be compiled without errors.
This more lenient approach may produce type errors at runtime.
The problem of determining if a storage location may be
accessed in more than one way is called alias analysis.
Two references are aliased if they point to the same object.
Alias analysis is mainly used for compiler optimizations.
We use it to know the concrete types a reference may be pointing to.
The following example uses the Wrapper
class previously shown:
class
Test {private var
testField;public void
setField(var
param) {this
.testField = param; }public var
getField() {return this
.testField; }public static void
Main() {var
wrapper =new
Wrapper("hi"
);var
test =new
Test(); test.setField(wrapper);string
s = test.getField().get();// * Correct!
wrapper.set(true);bool
b = test.getField().get();// * Correct!
string
s = test.getField().get();// * Compiler Error
} }
Initially, the wrapper
reference points to a string
object.
Then we create a Test
object that references the original Wrapper
object.
If we get the object inside the wrapper
object inside the test
object,
we get a string
object. Then we set a bool
attribute to the wrapper
object.
Repeating the previous access to the object inside the wrapper
object
inside the test
object, we get a bool
object!
The alias analysis implemented is type-based (uses type information to compute alias), inter-procedural (makes use of inter-procedural flow information), context-sensitive (differentiates between different calls to the same method), and maybe alias (detects all the objects a reference may point to; opposite to must point to).
If you want to start playing with StaDyn, it is time to download it from GitHub and start coding.