IfItQuacks 1.3.0

dotnet add package IfItQuacks --version 1.3.0
                    
NuGet\Install-Package IfItQuacks -Version 1.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IfItQuacks" Version="1.3.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IfItQuacks" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="IfItQuacks">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IfItQuacks --version 1.3.0
                    
#r "nuget: IfItQuacks, 1.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IfItQuacks@1.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IfItQuacks&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=IfItQuacks&version=1.3.0
                    
Install as a Cake Tool

<p align="center"> <img src="https://raw.githubusercontent.com/linkdotnet/IfItQuacks/main/assets/showcase.webp" alt="IfItQuacks: any class and anonymous types satisfy an interface, mismatches fail the build" /> </p>

IfItQuacks

Nuget GitHub tag

Compile-time checked structural (duck) typing for C#: "If it walks like a duck and quacks like a duck, it's a duck."

Getting Started

PM> Install-Package IfItQuacks

Declare an interface, mark a method as duck-typed and pass in anything that fits - no attributes on the interface or the types:

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 implements INamed. The generator verifies at compile time that both have a matching Name and redirects the call through small generated adapters - no reflection, no dynamic. A type that doesn't fit is a build error.

To keep a duck-typed value around, convert it explicitly:

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

Highlights

Anonymous types

Like object literals in TypeScript, anonymous types are ducks too - perfect for tests and quick stubs:

new Greeter().Greet(new { Name = "Steven" }, new Mallard());

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

Compatible, not identical

Members only have to fit, just like an assignment would:

public interface IInventory
{
    IEnumerable<string> Items { get; }
    long Count();
    void Add(string item);
}

public class Warehouse
{
    public List<string> Items { get; } = [];                  // List<string> -> IEnumerable<string>
    public int Count() => Items.Count;                        // int -> long
    public bool Add(object item) { Items.Add($"{item}"); return true; } // string -> object, result discarded
}

Test doubles without a mocking library

A member holding a delegate satisfies an interface method, Duck.Stub throws for everything that is missing, and Duck.Merge replaces a single member of a real object:

var repository = Duck.Stub<IRepository>(new { Find = (Func<int, Order?>)(id => new Order(id, "Rubber duck")) });

Order? saved = null;
var spy = Duck.Merge<IRepository>(new { Save = (Action<Order>)(order => saved = order) }, realRepository);

Copies, not only views

Duck.As returns a view that forwards; Duck.To builds a value of its own, verified the same way:

public record CustomerDto(string Name, string Email);

var dto = Duck.To<CustomerDto>(customer); // new CustomerDto(customer.Name, customer.Email)

Fields count as properties

public class LegacyPerson { public string Name = ""; }

new Greeter().Greet(new LegacyPerson { Name = "Steven" }, new Mallard());

Identity is preserved

Adapters forward Equals, GetHashCode and ToString to the original instance, so they work as dictionary keys and in sets. Duck.Unwrap gives you the original back:

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, ...
  • Extension methods: [DuckTyped] static string Greet(this INamed n) makes person.Greet() work
  • Sequences are adapted element by element, so a List<Person> is an IEnumerable<INamed>
  • Interface parameters that don't 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 - and [DuckTyped] methods as delegates
  • static abstract members and operators through 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

Documentation

Runnable examples live in samples.

Support & Contributing

Thanks to all contributors and people that are creating bug-reports and valuable input:

<a href="https://github.com/linkdotnet/IfItQuacks/graphs/contributors"> <img src="https://contrib.rocks/image?repo=linkdotnet/IfItQuacks" alt="Supporters" /> </a>

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 8 9/18/2026
1.2.0 35 9/17/2026
1.1.0 38 9/17/2026
1.0.0 39 9/17/2026
1.0.0-preview.1 33 9/17/2026