Duck Typing in C#
"when I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck" -- James Whitcomb Riley
It's really easy to implement duck typing in C# 4.0 since it introduced dynamic into its toolsets. The following is just some scratch to help me familiar with the dynamic feature (plus dynamic proxy from castle project )

using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
namespace DynamicDuckTypeing
{
class DynamicWrapper : DynamicObject
{
object _obj;
Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public DynamicWrapper(object obj)
{
_obj = obj;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return TrySetMember(binder.Name, value);
}
internal virtual bool TrySetMember(string name, object value)
{
var memberInfo = _obj.GetType().GetMember(name);
if (memberInfo.Count() == 0)
{
_dictionary[name] = value;
}
else
{
_obj.GetType().InvokeMember(name,
System.Reflection.BindingFlags.SetProperty,
null,
_obj,
new object[] { value });
}
return true;
}
internal virtual bool TryGetMember(string name, out object result)
{
var memberInfo = _obj.GetType().GetMember(name);
if (memberInfo.Count() == 0)
{
return _dictionary.TryGetValue(name, out result);
}
else
{
bool invokeSucceed = true;
try
{
result = _obj.GetType().InvokeMember(name,
System.Reflection.BindingFlags.GetProperty,
null,
_obj,
null);
}
catch
{
result = null;
invokeSucceed = false;
}
return invokeSucceed;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return TryGetMember(binder.Name,out result);
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
return TryInvokeMember(binder.Name, args, out result);
}
internal bool TryInvokeMember(string memberName, object[] args, out object result)
{
_obj.GetType().InvokeMember(memberName,
System.Reflection.BindingFlags.InvokeMethod,
null,
_obj, args);
result = null;
return true;
}
public override bool TryConvert (ConvertBinder binder, out object result)
{
result = Generator.GenerateProxy(binder.Type,this);
return true;
}
}
}
using System.Dynamic;
using System.Linq;
namespace DynamicDuckTypeing
{
class DynamicWrapper : DynamicObject
{
object _obj;
Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public DynamicWrapper(object obj)
{
_obj = obj;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return TrySetMember(binder.Name, value);
}
internal virtual bool TrySetMember(string name, object value)
{
var memberInfo = _obj.GetType().GetMember(name);
if (memberInfo.Count() == 0)
{
_dictionary[name] = value;
}
else
{
_obj.GetType().InvokeMember(name,
System.Reflection.BindingFlags.SetProperty,
null,
_obj,
new object[] { value });
}
return true;
}
internal virtual bool TryGetMember(string name, out object result)
{
var memberInfo = _obj.GetType().GetMember(name);
if (memberInfo.Count() == 0)
{
return _dictionary.TryGetValue(name, out result);
}
else
{
bool invokeSucceed = true;
try
{
result = _obj.GetType().InvokeMember(name,
System.Reflection.BindingFlags.GetProperty,
null,
_obj,
null);
}
catch
{
result = null;
invokeSucceed = false;
}
return invokeSucceed;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return TryGetMember(binder.Name,out result);
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
return TryInvokeMember(binder.Name, args, out result);
}
internal bool TryInvokeMember(string memberName, object[] args, out object result)
{
_obj.GetType().InvokeMember(memberName,
System.Reflection.BindingFlags.InvokeMethod,
null,
_obj, args);
result = null;
return true;
}
public override bool TryConvert (ConvertBinder binder, out object result)
{
result = Generator.GenerateProxy(binder.Type,this);
return true;
}
}
}

using System;
using Castle.DynamicProxy;
using Castle.Core.Interceptor;
namespace DynamicDuckTypeing
{
class Generator
{
static readonly ProxyGenerator _generator = new ProxyGenerator();
internal static object GenerateProxy(Type type, DynamicWrapper dynamicWrapper)
{
return _generator.CreateInterfaceProxyWithoutTarget(type, new Interceptor(dynamicWrapper));
}
}
class Interceptor : IInterceptor
{
DynamicWrapper _wrapper;
public Interceptor(DynamicWrapper wrapper)
{
_wrapper = wrapper;
}
public void Intercept(IInvocation invocation)
{
object result;
//property access will be converted to get_XXX and set_XXX, use String.Substring(4) to get the real proertyName
if (invocation.Method.Name.StartsWith("get_"))
{
invocation.ReturnValue = _wrapper.TryGetMember(invocation.Method.Name.Substring(4), out result);
invocation.ReturnValue = result;
}
else if (invocation.Method.Name.StartsWith("set_"))
{
_wrapper.TrySetMember(invocation.Method.Name.Substring(4), invocation.Arguments[0]);
}
else
{
_wrapper.TryInvokeMember(invocation.Method.Name, invocation.Arguments, out result);
invocation.ReturnValue = result;
}
}
}
}
using Castle.DynamicProxy;
using Castle.Core.Interceptor;
namespace DynamicDuckTypeing
{
class Generator
{
static readonly ProxyGenerator _generator = new ProxyGenerator();
internal static object GenerateProxy(Type type, DynamicWrapper dynamicWrapper)
{
return _generator.CreateInterfaceProxyWithoutTarget(type, new Interceptor(dynamicWrapper));
}
}
class Interceptor : IInterceptor
{
DynamicWrapper _wrapper;
public Interceptor(DynamicWrapper wrapper)
{
_wrapper = wrapper;
}
public void Intercept(IInvocation invocation)
{
object result;
//property access will be converted to get_XXX and set_XXX, use String.Substring(4) to get the real proertyName
if (invocation.Method.Name.StartsWith("get_"))
{
invocation.ReturnValue = _wrapper.TryGetMember(invocation.Method.Name.Substring(4), out result);
invocation.ReturnValue = result;
}
else if (invocation.Method.Name.StartsWith("set_"))
{
_wrapper.TrySetMember(invocation.Method.Name.Substring(4), invocation.Arguments[0]);
}
else
{
_wrapper.TryInvokeMember(invocation.Method.Name, invocation.Arguments, out result);
invocation.ReturnValue = result;
}
}
}
}

using System;
namespace DynamicDuckTypeing
{
public interface IQuack
{
void Quack();
}
class Duck
{
public void Quack()
{
Console.WriteLine("Duck Quack");
}
}
class Goose
{
public void Quack()
{
Console.WriteLine("Goose goose");
}
}
class Program
{
static void Main(string[] args)
{
dynamic dynamicDuck = new DynamicWrapper( new Duck());
dynamicDuck.Name = "mallard duck";
dynamicDuck.Age = 2;
Console.WriteLine("Name: {0}, Age: {1}",dynamicDuck.Name, dynamicDuck.Age);
dynamicDuck.Quack();
Console.WriteLine();
IQuack q = dynamicDuck;
q.Quack();
dynamic dynamicGoose = new DynamicWrapper(new Goose());
q = dynamicGoose;
q.Quack();
q = (dynamic)new DynamicWrapper(new object());
try
{
q.Quack();
Console.WriteLine("can't be there");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
namespace DynamicDuckTypeing
{
public interface IQuack
{
void Quack();
}
class Duck
{
public void Quack()
{
Console.WriteLine("Duck Quack");
}
}
class Goose
{
public void Quack()
{
Console.WriteLine("Goose goose");
}
}
class Program
{
static void Main(string[] args)
{
dynamic dynamicDuck = new DynamicWrapper( new Duck());
dynamicDuck.Name = "mallard duck";
dynamicDuck.Age = 2;
Console.WriteLine("Name: {0}, Age: {1}",dynamicDuck.Name, dynamicDuck.Age);
dynamicDuck.Quack();
Console.WriteLine();
IQuack q = dynamicDuck;
q.Quack();
dynamic dynamicGoose = new DynamicWrapper(new Goose());
q = dynamicGoose;
q.Quack();
q = (dynamic)new DynamicWrapper(new object());
try
{
q.Quack();
Console.WriteLine("can't be there");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?