6

C# is considered a statically-typed language. However, it contains keywords such as:

  • var, which infers the type at compile time, and
  • dynamic, which determines the type at runtime.

Is this a contradiction?

14
  • 45
    Note that var infers the type in compile type. This is also available in Java, Kotlin, Scala and other languages. The type in this case is still 100% static.
    – Borjab
    Dec 2, 2022 at 12:35
  • 17
    How does var "contradict the principle"? If local variable type inference contradicts static typing, then global type inference definitely contradicts static typing, which means that Haskell is not statically typed. I question whether any definition of "static typing" which excludes Haskell is useful. Dec 2, 2022 at 14:15
  • 5
    C API's in 'nix pass around void pointers all the time. So there's a long history of statically typed languages occasionally using dynamically typed data by some means.
    – davolfman
    Dec 2, 2022 at 21:17
  • 5
    "Statically typed" doesn't mean "you have to write the types explicitly". It means that types classify things that exist in the source code: variables and expressions. Dynamically typed means that types classify things that exist at runtime: values. There are hybrid systems where both kinds of types exist, and because there is a correspondence between code and runtime (there better be for a predictable system!), people will talk about the "type of a value" even in a purely static type system, or the "type of a variable/expression" even in a purely dynamic type system. But that is the core.
    – Ben
    Dec 2, 2022 at 22:42
  • 3
    FWIW, the terminology around this is a disaster: 'dynamic typing' has no sensible definition, and is used variously to refer to un-typed objects, late-binding/dynamic dispatch (which is hugely popular in 'statically typed' languages), runtime-modifiable types (all dynamic features of languages), and more often than not is just used loosely to refer to the runtime behaviour of languages like python which I would argue has nothing to do with types. The term 'static typing' isn't much better. Dec 3, 2022 at 9:11

5 Answers 5

31

C# has a static type system. So it is reasonable to describe it as a statically typed language. Just like any other OOP language, it also provides for some degree of dynamic typing. C# goes a lot further here than other statically typed languages by providing a dynamic static type, but this does not break its static type system.

Background:

With dynamic typing, values or objects have types. E.g. 1 is an int, and the result of evaluating new Foo() is an object of type Foo. We can think of every object being “tagged” with a type.

With static typing, expressions in our code can be assigned types. For example, x + 1 might be an int, and the variable foo might be declared to only contain Foo instances. These static types are guarantees about the values that the expression might evaluate to. This is useful because it might detect potential bugs, before running the code. Or viewed another way: a static type system allows for basic proofs of correctness. Static type systems also enable interesting features such as method overloading.

A lot of time, the static type of an expression is obvious. We don't have to manually annotate every expression with its static type. The compiler has rules to infer static types itself. When declaring a variable, we can instruct the compiler to infer its type with the var keyword.

With statically typed OOP languages, we have both static and dynamic typing, due to the use of inheritance and interfaces. Now, the static types do not describe exactly what values are possible at runtime, but only constrain them. For example, we might define a variable IFoo foo = .... This guarantees us statically that any object referred to by foo will conform with the IFoo interface, providing certain properties or methods. But we do not know which classes those objects might have exactly – we don't yet know their dynamic types.

C# has a unified inheritance hierarchy, with all objects being derived from the System.Object class. If we declare a variable with static type Object, then we can assign anything to it. However, we are only guaranteed that it will provide the methods that are part of the Object class, such as ToString(). If we want to call other methods that the object instance supports, we have to cast it to its actual type, which requires checking its dynamic type. We can do that via a cast. Casts let us write statically checked code dependent on the object's dynamic types, because casts can fail – the code can only be executed if the object really has the expected type.

A lot of code doesn't benefit that much from static type safety. As a shortcut for casting the object to the correct type before accessing a method or property, C# offers the dynamic type. This pseudy-type behaves differently from other static types, since it just defers any type checks to runtime. However, this is still perfectly safe. This cannot be used to violate the static types in other parts of the code, because the compiler will insert the suitable checks at runtime.

For example, consider a function that converts a dynamic object to an integer:

static int IntFromDynamic(dynamic o) => o;

This function is compiled roughly as if it were the following:

static int IntFromDynamic(Object o) => (int)o;

Neither variant would allow us to break the static type system, for example by trying to launder a string into an int: IntFromDynamic("foo") will fail at runtime.

15
  • 4
    @sanyassh No, Python has no method overloading in this sense that an expression f(x) might invoke different functions depending on the static type of x. What is possible: (1) You can annotate a function with multiple static type signatures using typing.overload. But these annotation have no effect on the program's behaviour. (2) You can have a function that inspects the dynamic types of its arguments and does different things depending on these types – allowing you to emulate overloading. The functools.singledispatch decorator implements a variant of this pattern.
    – amon
    Dec 3, 2022 at 14:08
  • 1
    @Dai: Wait until you find out about the unsafe keyword. It's well named.
    – Joshua
    Dec 5, 2022 at 1:26
  • 1
    @Dai whilst "does what you expect or throws" is less safe than "does what you expect or is a type error", but there are a lot of things like that in C#, not just casts. And the "does what you expect or nasal daemons" is demarcated by unsafe
    – Caleth
    Dec 5, 2022 at 14:23
  • 1
    @Dai my point is that "or throw" is still safe. The exception is caught somewhere, with well-defined semantics.
    – Caleth
    Dec 5, 2022 at 14:31
  • 1
    @Dai Terminating the process is a well-defined semantic
    – Caleth
    Dec 5, 2022 at 15:08
28

C# is flexible and versatile language that is not 100% bound to a specific paradigm. At its core it is statically typed but also has dynamic features, as you have correctly noted. There is no contradiction here and no issue to solve.

6
  • 5
    This. Sometimes, the correct answers are just that simple - no need to search for some hidden reasons or conspiracy.
    – Doc Brown
    Dec 2, 2022 at 13:30
  • Saying it's not bound to a specific paradigm doesn't really answer the question. If it doesn't adhere to static typing only it can't just be called to have a static type system.
    – Jimmy T.
    Dec 3, 2022 at 13:48
  • 1
    @JimmyT. Pay attention to semantics! Just because you have something, doesn't mean that you don't also have something else.
    – R. Schmitz
    Dec 3, 2022 at 15:27
  • @R.Schmitz but it's about what it is, not what it has
    – Jimmy T.
    Dec 4, 2022 at 14:03
  • 2
    @JimmyT. "Static typing" is just a label used to identify common traits between languages - not some cosmic law. As such, C# is statically typed, even if in some small respects it's also dynamically typed. Dec 5, 2022 at 0:46
16

Per the comment, "var" just means "infer the static type as if I'd written it here". It compiles as if you'd written a specific type there.

Dynamic is a little different. Let's see what it says in the documentation:

The dynamic type is a static type, but an object of type dynamic bypasses static type checking. In most cases, it functions like it has type object. At compile time, an element that is typed as dynamic is assumed to support any operation.

Look right there: "dynamic" is a static type!

That is, the precise type of the object is to be determined at runtime, but the fact that it's dynamic is recorded in the static type system.

You can even see what this gets turned into in MSIL. The method invocations against the dynamic object are done using reflection.

6
  • Basically interacting with a dynamic type generates MSIL that utilizes class reflection. Dec 2, 2022 at 13:19
  • 5
    Sorry, but I think this is hairsplitting. From the POV of a dev who just uses the language the keyword dynamic allows dynamic typing (regardless of how it is implemented or that MS calls dynamic a static type).
    – Doc Brown
    Dec 2, 2022 at 13:29
  • 4
    Well, the question is hairsplitting; Rumien's "there is no contradiction" answer is also valid
    – pjc50
    Dec 2, 2022 at 14:45
  • 1
    Being able to call just any method on a variable without compile time error is what makes a dynamically typed language.
    – Jimmy T.
    Dec 3, 2022 at 13:54
  • 1
    @XY Yes, you're correct that that is the output from the decompiler. It's transformed into the "static" type "object"
    – pjc50
    Dec 12, 2022 at 9:56
2

From a compiler/language theory perspective, you're confusing static/dynamic typing with implicit/explicit typing. Implicit typing is also called type inference.

In an explicitly typed language, the programmer has to explicitly declare the type of every variable. In an implicitly typed language, types can be inferred by the compiler. It is important to note that in principle implicit/explicit typing has nothing to do with static/dynamic typing. You can have the combinations: static/explicit, static/implicit, and dynamic/implicit.

The classic statically typed, explicitly typed language is C. Everything must be explicitly typed, and the compiler assumes nothing. Every variable definition looks something like:

typedef varname = r_value;

or for example:

char* x = "Hello, world";
int   y = 42;
int   z = 0; //Depends on which C standard you're using, but in general
             //0 is false and any nonzero value is true 

A statically typed, implicitly typed language is Rust. The philosophy of Rust is to empower programmers, so while the static type system is strict, it also doesn't require you to be overly-explicit. Statements like:

let x = "Hello, world";
let y = 42;
let z = false;

are perfectly clear to the programmer and compiler, so they are implicitly typed. You can write them explicitly if you prefer or need to:

let x: &str = "Hello, world";
let y: u16  = 42;
let z: bool = false;

The two easy examples for dynamically typed, implicitly typed languages are Python or Javascript. In Python the principle is duck-typing - or, if it walks like a duck and quacks like a duck, it's a duck. In Python it is perfectly valid to write the following code:

x = "Hello, world";
x = 42;
x = false;

In a statically typed language, the type checker would error out on the second line, because once x is assigned a string type, it cannot hold a numeric value.

Note that there are some other topics that crop up here that can conflate the issue. Rust is statically typed, but it does allow redefinition of variables, so the result is that you can write code that looks like dynamically typed code, even though it's entirely static. In Rust, this code fails to compile with a type error:

let x = "Hello, world!";
x = 42;

However, also in Rust, this code does compile:

let x = "Hello, world!";
let x = 42;

The difference is the let keyword. This explicitly redeclares the variable x and when this happens the new version of x is allowed to have a different type from the old version. Both versions of x are statically typed at compile time, so while this isn't great coding style, it's technologically no more complex than declaring a new variable with any other name and doesn't require any special support.

Ultimately there is no clear answer about what kind of system is best. The choice can dramatically limit the kind of code you can write, and especially impacts the job of the compiler/runtime implementer. In particular, dynamically typed means that types are checked at runtime, at the point of evaluating expressions. For example, the following Python code runs without errors:

x = "five"
if isinstance(x, int):
    y = x + 1

This code runs just fine in Python, because the if statement is false and the following line never executes. (If it did execute, this program would throw a TypeError and stop running.) However, a statically typed language like Rust would choke and die at compile time on the third line at the type-checking stage, because x is a string and you can't add 1 to a string, regardless of the fact that later on a reachability analysis could show that this code never executes.

This has a lot of other knock-on effects as well. If you've ever wondered how an interpreted language could literally be 100 times slower than C or C++, typing plays a huge role. In Python, every single operation like f() + g() needs to run some code that verifies the types of the left and right operands and potentially does some dynamic typing as well. In C/C++/Rust, the static type checker already verified at compile time that f() and g() return the proper types, and could potentially implement f() + g() in as little as a single assembly instruction.

C# is very much an odd exception in that it allows both static and dynamic typing in the same language. First, note that var does not violate static typing- this is implicit typing. The type is decided by the compiler at compile time, and then that is enforced. However, dynamic definitely does violate static typing rules. It is made clear that the default and standard mode is static typing, with dynamic being used to add more flexibility in certain corner cases that are expected to be less common. Writing large parts of your program with dynamic is probably a good sign that you're doing something against the intention of the language designer.

1
  • 1
    The distinction between static/dynamic and implicit/explicit is a great thing for me, it removed a great part of the contribution, also the mention that you can have 3 combinations static/explicit, static/implicit, and dynamic/implicit. I can say that your answer is the second correct answer for my question.
    – X Y
    Dec 9, 2022 at 14:34
-1

All C# codebases use static typing, so C# is considered a mostly statically-typed language.

It also has support for optional dynamic typing, but many codebases don't use it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.