Duck typing in the C# compiler
There's nothing special about IDisposable
here - but there is something special about iterators.
Before C# 2, using this duck type on foreach
was the only way you could implement a strongly-typed iterator, and also the only way of iterating over value types without boxing. I suspect that if C# and .NET had had generics to start with, foreach
would have required IEnumerable<T>
instead, and not had the duck typing.
Now the compiler uses this sort of duck typing in a couple of other places I can think of:
- Collection initializers look for a suitable
Add
overload (as well as the type having to implementIEnumerable
, just to show that it really is a collection of some kind); this allows for flexible adding of single items, key/value pairs etc - LINQ (
Select
etc) - this is how LINQ achieves its flexibility, allowing the same query expression format against multiple types, without having to changeIEnumerable<T>
itself - The C# 5 await expressions require
GetAwaiter
to return an awaiter type which hasIsCompleted
/OnCompleted
/GetResult
In both cases this makes it easier to add the feature to existing types and interfaces, where the concept didn't exist earlier on.
Given that IDisposable
has been in the framework since the very first version, I don't think there would be any benefit in duck typing the using
statement. I know you explicitly tried to discount the reasons for having Dispose
without implementing IDisposable
from the discussion, but I think it's a crucial point. There need to be good reasons to implement a feature in the language, and I would argue that duck typing is a feature above-and-beyond supporting a known interface. If there's no clear benefit in doing so, it won't end up in the language.
Can the C# compiler use duck typing with foreach over a generic type?
No, basically. The only use for "B" is when the TList
itself is actually a struct
; the IL can then use "constrained call" to call the original GetEnumerator()
without any part having to box the original struct TList
value.
But: once you've called GetEnumerator()
, you're back into IEnumerator<T>
land, and it will not use the custom iterator.
All of which is mostly moot in this case, because iterator blocks are also fairly "allocatey". So... if avoiding boxing the TList
is your concern, you are presumably obsessive about allocations: in which case you wouldn't write the iterator block this way, either.
What is Interface Duck Typing?
Duck typing allows an object to be passed in to a method that expects
a certain type even if it doesn’t inherit from that type. All it has
to do is support the methods and properties of the expected type in
use by the method. I emphasize that last phrase for a reason. Suppose
we have a method that takes in a duck instance, and another method
that takes in a rabbit instance. In a dynamically typed language that
supports duck typing, I can pass in my object to the first method as
long as my object supports the methods and properties of duck in use
by that method. Likewise, I can pass my object into the second method
as long as it supports the methods and properties of rabbit called by
the second method. Is my object a duck or is it a rabbit? Like the
above image, it’s neither and it’s both. In many (if not most) dynamic
languages, my object does not have to support all methods and
properties of duck to be passed into a method that expects a duck.
Same goes for a method that expects a rabbit.It only needs to support
the methods and properties of the expected type that are actually
called by the method.
Please refer this to get an idea about Duck Typing
http://haacked.com/archive/2007/08/19/why-duck-typing-matters-to-c-developers.aspx/
When to use Duck Typing?
C# has strong typing for a reason. Unless you have a valid reason (such as needing COM interop) to use the dynamic
type, you should probably avoid it like the plague, or you'll risk turning compile-time problems into runtime problems. dynamic
is powerful, but easy to abuse. Think long and hard about if you really need dynamic typing - if you think there is, there's a chance that you're approaching the problem wrong to begin with, and need to refactor your code.
To specifically answer your question - one potential use case was if you were writing serialisation code and needed to accept a deserialised object, for example a deserialised JSON structure from a web API request. Such methods would need to handle any type given to them, which is a situation in which using dynamic
is better than the alternative (ie. a truck-load of reflection).
Another example I can think of would be interoperability with languages specifically on the Dynamic Language Runtime (eg. JavaScript, IronPython, IronRuby, ...), and needed to write a method accepting types from such languages.
From Beginning Visual C# 2012 Programming:
For most C# code that you write, avoid the
dynamic
keyword. However, if a situation arises where you need to use it, use it and love it - and spare a thought for those poor programmers of the past who didn't have this powerful tool at their disposal.
Using Java and C#'s generics to simulate duck typing
can C#'s generics actually achieve the same thing as duck typing?
No. But C#'s generics can include a constraint where the type parameter is restricted to inherit or implement some particular type. When that's done, any expression of the type of that type parameter is resolved as the constrained type and members from that type can be accessed.
This is similar to the extends
constraint described in the article you read.
The only duck-typing support in C# is the dynamic
keyword, where final compilation of expressions involving dynamic
values is deferred until runtime when the actual runtime type is known.
Related reading:
Trivial C# class with a generic parameter wouldn't compile for no apparent reason
Call a method of type parameter
Implementing safe duck-typing in C#
How can you know if a cow walks like a duck and quacks like a duck if you don't have a living, breathing cow in front of you?
Duck-typing is a concept used at run-time. A similar concept at compile-time is structural typing which is AFAIK not supported by the CLR. (The CLR is centred around nominative typing.)
[A structural type system] contrasts with nominative systems, where comparisons are based on explicit declarations or the names of the types, and duck typing, in which only the part of the structure accessed at runtime is checked for compatibility.
The usual way to ensure that duck-typing throws no exception at run-time are unit-tests.
Reusing the same function by casting, or duck typing different object types
Off the top of my head, your options are:
- Get both of the source classes to implement the same interface and pass that around instead of the concrete types. This would be the preferable option but I'm guessing not possible here.
- Deserialize and serialize back to convert between types. You already have this code, but like you say it may be slow.
- Use a mapping library such as AutoMapper to convert between types. This is very fast but requires you bring in an external library from Nuget (I've used AutoMapper many times)
- Manually map the properties yourself. This would probably be the fastest code but pretty awful to write.
Use
dynamic
all the way down the chain, not just at the top. You lose compile time type checking, but it should be reasonably fast. For example, instead of having a function like this:public static string _ExtractFoo(ProductService.Customer customer)
{
return customer.DoSomethingExciting();
}You would have this:
public static string _ExtractFoo(dynamic customer)
{
return customer.DoSomethingExciting();
}You could, if you wanted, add some checking to ensure that
customer
is eitherProductService.Customer
orCoreService.Customer
if you want some safety.
Duck typing doesn't implicitly convert a type to an interface variable
Unfortunately, C# or .NET does not support duck typing in this particular context.
For a type to implement a particular interface, it will have to explicitly declare that it implements this interface. You say "satisfy" now, but this has no meaning for C# or .NET.
Does the type MyClass
implement IInterface
? No, sadly it doesn't.
This declaration:
public class MyClass { }
Declares a class that inherits System.Object
, and does not implement any interface.
The fact that the interface contains no methods or properties does not in any way make it match up with this type. The class still does not implement this interface.
The only way for a class to implement an interface is to make it explicit:
public class MyClass : IInterface { }
So no, there is no way to force C# or .NET to consider that class as one implementing this interface.
The common ways to handle this is to make a wrapper class (ie. a class that implements IInterface
and contains a MyClass
, delegating all methods and/or properties to the contained MyClass
instance), or, you know, actually implement the interface.
To conclude, the only way to make this code compile:
IInterface myVariable = new MyClass();
is to make MyClass
explicitly either implement the interface:
class MyClass : IInterface { }
or to inherit from another class that implements it:
class BaseClass : IInterface { }
class MyClass : BaseClass { }
duck typing in D
This is actually a very common thing to do in D. It's how ranges work. For instance, the most basic type of range - the input range - must have 3 functions:
bool empty(); //Whether the range is empty
T front(); // Get the first element in the range
void popFront(); //pop the first element off of the range
Templated functions then use std.range.isInputRange
to check whether a type is a valid range. For instance, the most basic overload of std.algorithm.find
looks like
R find(alias pred = "a == b", R, E)(R haystack, E needle)
if (isInputRange!R &&
is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
{ ... }
isInputRange!R
is true
if R
is a valid input range, and is(typeof(binaryFun!pred(haystack.front, needle)) : bool)
is true
if pred
accepts haystack.front
and needle
and returns a type which is implicitly convertible to bool
. So, this overload is based entirely on static duck typing.
As for isInputRange
itself, it looks something like
template isInputRange(R)
{
enum bool isInputRange = is(typeof(
{
R r = void; // can define a range object
if (r.empty) {} // can test for empty
r.popFront(); // can invoke popFront()
auto h = r.front; // can get the front of the range
}));
}
It's an eponymous template, so when it's used, it gets replaced with the symbol with its name, which in this case is an enum of type bool
. And that bool
is true
if the type of the expression is non-void
. typeof(x)
results in void
if the expression is invalid; otherwise, it's the type of the expression x
. And is(y)
results in true
if y
is non-void
. So, isInputRange
will end up being true
if the code in the typeof
expression compiles, and false
otherwise.
The expression in isInputRange
verifies that you can declare a variable of type R
, that R
has a member (be it a function, variable, or whatever) named empty
which can be used in a condition, that R
has a function named popFront
which takes no arguments, and that R
has a member front
which returns a value. This is the API expected of an input range, and the expression inside of typeof
will compile if R
follows that API, and therefore, isInputRange
will be true
for that type. Otherwise, it will be false
.
D's standard library has quite a few such eponymous templates (typically called traits) and makes heavy use of them in its template constraints. std.traits
in particular has quite a few of them. So, if you want more examples of how such traits are written, you can look in there (though some of them are fairly complicated). The internals of such traits are not always particularly pretty, but they do encapsulate the duck typing tests nicely so that template constraints are much cleaner and more understandable (they'd be much, much uglier if such tests were inserted in them directly).
So, that's the normal approach for static duck typing in D. It does take a bit of practice to figure out how to write them well, but that's the standard way to do it, and it works. There have been people who have suggested trying to come up with something similar to your Implements!(S, Interface)
suggestion, but nothing has really come of that of yet, and such an approach would actually be less flexible, making it ill-suited for a lot of traits (though it could certainly be made to work with basic ones). Regardless, the approach that I've described here is currently the standard way to do it.
Also, if you don't know much about ranges, I'd suggest reading this.
Related Topics
What Is the Correct Way to Read a Serial Port Using .Net Framework
App.Config for a Class Library
C# - Making a Process.Start Wait Until the Process Has Start-Up
Nullable Reference Types with Generic Return Type
Create Datatemplate in Codebehind
How to Seed an Admin User in Ef Core 2.1.0
Determine If Current Application Is Activated (Has Focus)
Hashset That Preserves Ordering
C# Implementation of Deep/Recursive Object Comparison in .Net 3.5
C# How to Test a File Is a Jpeg
Meanings of Declaring, Instantiating, Initializing and Assigning an Object
Convert Datetime for MySQL Using C#
Why Enums Require an Explicit Cast to Int Type
Possible to Calculate Md5 (Or Other) Hash with Buffered Reads
Using JSON.Net to Return Actionresult
How to Solve Operator '!=' Cannot Be Applied to Operands of Type 'T' and 'T'