---
title: "If It Quacks - Part 2"
description: "Some days ago I wrote about [duck-typing in C# with interceptors](https://steven-giesel.com/blogPost/5170e165-29e8-437a-b5ce-446a84943809/quack-quack-ducktyping-in-c-with-interceptors). Back then it was a proof-of-concept with a lot of rough edges. Now it is a real library: [IfItQuacks](https://github.com/linkdotnet/IfItQuacks)."
updated: 2026-09-18
tags: ["C#", ".NET"]
canonical: "https://steven-giesel.com/blogPost/41599817-4457-441e-b874-1a5c67f4e7cc/if-it-quacks-part-2"
---

# If It Quacks - Part 2

Some days ago I wrote about [duck-typing in C# with interceptors](https://steven-giesel.com/blogPost/5170e165-29e8-437a-b5ce-446a84943809/quack-quack-ducktyping-in-c-with-interceptors). Back then it was a proof-of-concept with a lot of rough edges. Now it is a real library: [IfItQuacks](https://github.com/linkdotnet/IfItQuacks).

## The idea in 30 seconds

Declare an interface, mark your method with `[DuckTyped]` and pass in anything that *fits* - no base class, no `implements`, no attributes on the types themselves:

```csharp
using IfItQuacks;

public interface INamed
{
    string Name { get; }
}

public class Person { public string Name => "Steven"; }
public class Mallard { public string Name => "Donald"; }

public partial class Greeter
{
    [DuckTyped]
    public string Greet(INamed first, INamed second, string greeting = "Hello") =>
        $"{greeting}, {first.Name} and {second.Name}!";
}

new Greeter().Greet(new Person(), new Mallard()); // Hello, Steven and Donald!
```

Neither `Person` nor `Mallard` knows about `INamed`. The source generator checks at compile time that both have a matching `Name` and routes the call through a small generated adapter. No reflection, no `dynamic`. If it doesn't fit, you get an analyzer error.

If you want to hold on to the duck, convert it explicitly:

```csharp
List<INamed> names = [Duck.As<INamed>(new Person()), Duck.As<INamed>(new Mallard())];
```

## Highlights

Here is the short tour. The [documentation](https://linkdotnet.github.io/IfItQuacks/) has the long one.

### Anonymous types are ducks

This is my favorite one. Anonymous types are object literals, basically the same thing TypeScript hands you and they quack just fine:

```csharp
new Greeter().Greet(new { Name = "Steven" }, new { Name = "Donald" });
```

Basically, you can write your tests with that. "Instant Stubs"!

```csharp
public interface IPerson
{
    string Name { get; }
    int Age { get; }
}

public partial class AgeChecker
{
    [DuckTyped]
    public bool IsAdult(IPerson person) => person.Age >= 18;
}

[Test]
public void Should_detect_adults()
{
    var checker = new AgeChecker();

    Assert.That(checker.IsAdult(new { Name = "Steven", Age = 90 }), Is.True);
    Assert.That(checker.IsAdult(new { Name = "Baby Duck", Age = 1 }), Is.False);
}
```

Or:

```csharp
IPerson stub = Duck.As<IPerson>(new { Name = "Donald", Age = 90 });

List<IPerson> family =
[
    Duck.As<IPerson>(new { Name = "Huey", Age = 10 }),
    Duck.As<IPerson>(new { Name = "Dewey", Age = 10 }),
    Duck.As<IPerson>(new { Name = "Louie", Age = 10 }),
];
```

### Identity is preserved

Adapters forward `Equals`, `GetHashCode` and `ToString` to the wrapped instance, so they behave in dictionaries and sets. And you can get the original back:

```csharp
var person = new Person();
Duck.As<INamed>(person).Equals(Duck.As<INamed>(person)); // true
Duck.Unwrap(Duck.As<INamed>(person)) is Person;          // true
```

### And more

* Any interface works, including framework ones: `Duck.As<IDisposable>(x)`, `IEnumerable<T>` parameters, ...
* Interface parameters that do **not** need duck typing (an `ILogger`, say) keep accepting implementations, `null` and default values
* Generic interfaces (`IContainer<T>`) and generic `[DuckTyped]` methods with inferred type arguments
* Events, indexers and default interface members
* Delegates, lambdas and method groups for single-method interfaces
* `static abstract` members and operators via duck-typed constraints (`where T : IAddable<T>`) - generic math included
* Instance and static methods, `ref`/`out`, `params`, default values and named arguments
* Zero setup: install the package, no project file changes

## Resources

* Repository: [https://github.com/linkdotnet/IfItQuacks](https://github.com/linkdotnet/IfItQuacks)
* Documentation: [https://linkdotnet.github.io/IfItQuacks/](https://linkdotnet.github.io/IfItQuacks/)
* [How does it work?](https://linkdotnet.github.io/IfItQuacks/articles/concepts.html) - including what gets allocated
* [Known limitations](https://linkdotnet.github.io/IfItQuacks/articles/known_limitations.html)
* The original blog post: [Quack Quack: Duck-Typing in C# with Interceptors](https://steven-giesel.com/blogPost/5170e165-29e8-437a-b5ce-446a84943809/quack-quack-ducktyping-in-c-with-interceptors)