gpt4 book ai didi

c# - 在不显式使用特定类的情况下传递实现接口(interface)的对象

转载 作者:行者123 更新时间:2023-12-05 05:35:44 38 4
gpt4 key购买 nike

我有一个带有 IMapper 类型参数的方法,我希望能够在不创建类的情况下向其传递实现 IMapper 的匿名类型,并明确告知该类必须使用分号实现接口(interface);

    public interface IMapper {
string Mail { get; set; }
}

public static void Mapper(IMapper mappable)
{
Console.WriteLine(mappable.Mail);
}


    public static void Main(string[] args)
{
var test = new { Mail = "test@gmail.com"};
Mapper(test);
}

最佳答案

I want to be able to pass Anonymous types on it that implement the IMapper

作为@Flydog57 said , C# 没有 structural typing (又名“鸭子”打字)。

此外,anonymous types无法实现接口(interface)。

更糟糕的是,匿名类型是不可变的!因此,即使他们可以实现接口(interface),他们也无法实现您的 IMapper 接口(interface),因为它有一个属性 setter 。

所以技术上正确的答案是:这是不可能的。

归根结底,我认为这只是另一个提醒,即 C# 的类型系统不如其他语言灵活或强大。

但是!我想到了 C# 领域的几个替代方案。

使用动态类型

public static void Mapper(dynamic mappable) // Notice "dynamic" type
{
Console.WriteLine(mappable.Mail);
}

public static void Main(string[] args)
{
var test = new { Mail = "test@gmail.com"};
Mapper(test);
}

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic

dynamic 类型绕过静态类型检查。这只是一种奇特的说法,无论您访问什么属性或在 mappable 上调用什么方法,您的代码都会编译。不过,这并不意味着如果您调用 mappable.ThisMethodDoesNotExist() 它将起作用!

缺点

  • IDE 不会帮助您重构属性/字段/方法名称
  • 编程错误稍后出现(在运行时而不是编译时)

使用 DispatchProxy 在运行时动态生成代理对象

这具有 dynamic 的所有缺点,同时速度更慢,但它非常强大。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

class Program
{
interface IMapping
{
string Mail { get; }
}

class Mapping : IMapping
{
public string Mail { get; set; } = string.Empty;
}

static void Main()
{
var anon = new
{
Mail = "Hello, world!"
};
IMapping duck = Quack.From(anon).LikeA<IMapping>();
Console.WriteLine(duck.Mail); // See? It works!

// Compare performance of various options

// First, using our proxy generated above
const int numRounds = 100_000_000;
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < numRounds; ++i)
{
GC.KeepAlive(duck.Mail);
}
var proxyTime = stopwatch.Elapsed;
Console.WriteLine($"Proxy: {numRounds / proxyTime.TotalSeconds:N} Hz"); // 16,943,811.22 Hz

// Second, using the `dynamic` type
var dyn = (dynamic)anon;
stopwatch.Restart();
for (var i = 0; i < numRounds; ++i)
{
GC.KeepAlive(dyn.Mail);
}
var dynamicTime = stopwatch.Elapsed;
Console.WriteLine($"Dynamic: {numRounds / dynamicTime.TotalSeconds:N} Hz"); // 30,946,883.98 Hz

// Third, using the idiomatic solution, which is to use a named type instead of an anonymous type
var real = new Mapping
{
Mail = "Hello, world!"
};
stopwatch.Restart();
for (var i = 0; i < numRounds; ++i)
{
GC.KeepAlive(real.Mail);
}
var realTime = stopwatch.Elapsed;
Console.WriteLine($"Real: {numRounds / realTime.TotalSeconds:N} Hz"); // 279,305,111.23 Hz -- this is what you're missing out on by trying to be so extravagant
}
}

sealed class Quack<TConcrete>
{
readonly TConcrete _concrete;

public Quack(TConcrete concrete)
{
_concrete = concrete;
}

public TInterface LikeA<TInterface>()
{
object proxy = DispatchProxy.Create<TInterface, MethodMappingProxy<TInterface, TConcrete>>()!;
((MethodMappingProxy<TInterface, TConcrete>)proxy).Load(_concrete);
return (TInterface)proxy;
}
}

static class Quack
{
public static Quack<TConcrete> From<TConcrete>(TConcrete concrete) => new(concrete);
}

class MethodMappingProxy<TInterface, TConcrete> : DispatchProxy
{
static readonly Lazy<IReadOnlyDictionary<MethodInfo, Func<TConcrete, object?[]?, object?>>> MethodMapping = new(InitializeMethodMapping);

TConcrete _instance = default!;

static IReadOnlyDictionary<MethodInfo, Func<TConcrete, object?[]?, object?>> InitializeMethodMapping()
{
var dictionary = new Dictionary<MethodInfo, Func<TConcrete, object?[]?, object?>>();
var concreteMethods = typeof(TConcrete)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(methodInfo => methodInfo.Name);
foreach (var methodInfo in typeof(TInterface).GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
// Remember, properties are just syntax sugar for underlying get/set methods

// Validate that this interface method exists in the concrete type, and that its signature matches
var name = methodInfo.Name;
if (!concreteMethods.TryGetValue(name, out var concreteMethodInfo))
throw new Exception($"Missing method {name}");
if (methodInfo.ReturnType != concreteMethodInfo.ReturnType)
throw new Exception($"{name} method has wrong return type");
var interfaceMethodParameters = methodInfo.GetParameters();
var concreteMethodParameters = concreteMethodInfo.GetParameters();
if (interfaceMethodParameters.Length != concreteMethodParameters.Length)
throw new Exception($"{name} method has wrong number of parameters");
for (var i = 0; i < interfaceMethodParameters.Length; ++i)
{
if (interfaceMethodParameters[i].ParameterType != concreteMethodParameters[i].ParameterType)
throw new Exception($"{name} method parameter #{i + 1} is wrong type");
}

// Set up the expression for calling this method on the concrete type. We'll wrap the concrete method
// invocation in a Func<TConcrete, object?[]?, object?> delegate
var instanceParameter = Expression.Parameter(typeof(TConcrete));
var argsParameter = Expression.Parameter(typeof(object?[]));
Expression body;
if (interfaceMethodParameters.Length == 0)
{
body = Expression.Call(instanceParameter, concreteMethodInfo);
}
else
{
var castArgs = concreteMethodParameters
.Select((parameterInfo, index) => (Expression)Expression.Convert(
Expression.ArrayAccess(argsParameter, Expression.Constant(index, typeof(int))),
parameterInfo.ParameterType
))
.ToArray();
body = Expression.Call(
instanceParameter,
concreteMethodInfo,
castArgs
);
}
var func = Expression.Lambda<Func<TConcrete, object?[]?, object?>>(body, instanceParameter, argsParameter).Compile();

// Squirrel away this function
dictionary[methodInfo] = func;
}
return dictionary;
}

protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
if (targetMethod is null)
return null;
var methodMapping = MethodMapping.Value;
if (!methodMapping.TryGetValue(targetMethod, out var func))
throw new InvalidOperationException();
return func(_instance, args);
}

public void Load(TConcrete instance)
{
GC.KeepAlive(MethodMapping.Value); // Ensure the mapping has been created, and throw any exceptions now instead of later
_instance = instance;
}
}

Run it here

使用 DispatchProxy 对象的有趣之处在于,您可以 Hook 方法调用并让它们在运行时执行任何您想要的操作。

我将把它留作练习,让读者弄清楚如何使上述内容适用于静态属性/方法。我只关注非静态属性和方法,因为匿名类型没有静态成员!

动态地将 IL 发射到动态编译的程序集中

而前面的选项让我想起了最后的核选项。您知道可以动态生成 .Net 程序集吗?您知道您可以在运行时加载这样的程序集吗?

您需要搜索的关键字是“C# IL generation”。

一旦你学会了如何做到这一点,世界就是你的牡蛎。您可以编写 C# 代码来编写自动声明和实例化匿名类型周围的精简包装类型所需的任何 C# 代码。

我将此作为练习留给读者。

关于c# - 在不显式使用特定类的情况下传递实现接口(interface)的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73428577/

38 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com