C# 我们可以获取代理的身份吗?

C# 我们可以获取代理的身份吗?,c#,.net,delegates,C#,.net,Delegates,是否可以生成一个委托的标识,以将其与其他委托区分开来?想想这段代码: Func<int, int, int> delegate1 = a, b => a + b; Func<int, int, int> delegate2 = a, b => a + b; Func<int, int, int> delegate3 = a, b => a - b; let id1 = id(delegate1); let id2 = id(delegate2

是否可以生成一个委托的标识,以将其与其他委托区分开来?想想这段代码:

Func<int, int, int> delegate1 = a, b => a + b;
Func<int, int, int> delegate2 = a, b => a + b;
Func<int, int, int> delegate3 = a, b => a - b;
let id1 = id(delegate1);
let id2 = id(delegate2);
let id3 = id(delegate3);
Assert(id1 == id2);
Assert(id1 != id3);
一种可能的解决方案是比较表达式字符串,但在捕获clouser时仍然存在问题,例如:

int c = 0;
Expression<Func<int, int, int>> delegate1 = (a, b) => a + b + c;
c += 1;
Expression<Func<int, int, int>> delegate2 = (a, b) => a + b + c;
Expression<Func<int, int, int>> delegate3 = (a, b) => a - b - c;
Console.WriteLine(delegate1);
Console.WriteLine(delegate2);
Console.WriteLine(delegate1.ToString() == delegate2.ToString());
Console.ReadKey();
一种(相对简单的)方法是使用
Expression
s而不是
Func
本身,因为它们有更详细的信息,可以让你分析东西。例如

Expression<Func<int, int, int>> delegate1 = (a, b) => a + b;
Expression<Func<int, int, int>> delegate2 = (a, b) => a + b;
Expression<Func<int, int, int>> delegate3 = (a, b) => a - b;
var id1 = id(delegate1);
var id2 = id(delegate2);
var id3 = id(delegate3);
Debug.Assert(id1 == id2);
Debug.Assert(id1 != id3);
通过测试


请注意,这不是一个完整的解决方案,并且有很多问题。如果需要进行全面比较,则需要考虑表达式的全部性质,包括(但不限于)表达式中的类型、方法调用、闭包访问等。。
我不认为这在一般情况下是可以解决的,但通常它可以被限制在一些更特殊的情况下,可以解决。

您需要在这里的
表达式
级别工作。C#编译器不保证相同的lambda最终使用相同的委托对象。目前未执行此优化,但存在GitHub问题。即使执行了该操作,也会一次处理一个程序集,这可能对您来说还不够。如果委托捕获了闭包值,那么这将永远不起作用

我曾经这样做是为了在给定查询的情况下自动缓存LINQ2SQL编译的查询。比较表达式树并非易事
ToString
不是完全逼真的,而且速度很慢。您需要编写自己的比较器类。我认为网络上有这样的代码作为起点

例如,在比较常量表达式时,不能只执行
ReferenceEquals(val1,val2)
。实际上,您必须对许多类型(如装箱基元类型)进行特殊处理。它们可以具有相同的值,但使用不同的对象标识装箱

您还需要编写哈希代码函数,因为您可能希望使用哈希表查找缓存结果

您还将遇到GC问题,因为表达式树可以保留任意对象。基本上,闭包可以以意外的方式随机保留局部变量。所以我所做的就是在缓存树之前对它们进行消毒。我删除了所有不安全的常量

是否可以生成一个委托的标识以将其与其他委托区分开来


是的,但它涉及到手动比较和散列表达式树。

这里的一个选项是使用
字典
保存字符串ID和我们的委托(字符串键包含排序表达式,该表达式与来自某个测试调用的值连接[这会生成某种类型的UID]),值是我们的Func委托。只有在将转换为字符串的表达式映射到
\u delegatesMapping
中的ID时,表达式才会第一次编译:

public class Funker
{
    private static Dictionary<string, string> _delegatesMapping;
    private static Dictionary<string, Func<int, int, int>> _delegates;
    public static Funker Instance { get; private set; }

    static Funker()
    {
        _delegatesMapping = new Dictionary<string, string>();
        _delegates = new Dictionary<string, Func<int, int, int>>();
        Instance = new Funker();
    }

    private Funker() { }

    public Func<int, int, int> this[Expression<Func<int, int, int>> del]
    {
        get
        {
            string expStr = del.ToString();

            var parameters = del.Parameters;

            for (int i = 0; i < parameters.Count; i++)
                expStr = expStr.Replace(parameters[i].Name, String.Concat('_' + i));

            Func<int, int, int> _Del = null;

            if (!_delegatesMapping.ContainsKey(expStr))
            {
                _Del = del.Compile();
                _delegatesMapping.Add(expStr, new String(expStr.OrderBy(ch => ch).ToArray()) + "|" + _Del(55, 77));
            }

            if (!_delegates.ContainsKey(_delegatesMapping[expStr])) _delegates.Add(_delegatesMapping[expStr], _Del ?? del.Compile());
            return _delegates[_delegatesMapping[expStr]];
        }
    }
}
输出:

true
false
附言。 但正如斯维科在回答中所说:

请注意,这不是一个完整的解决方案,有很多很多问题 问题。如果你需要一个全面的比较,你需要 考虑到表达的全部性质,包括(但不限于) 有限)表达式中进出的类型、方法调用、, 封闭通道等


也许您可以使用而不是
Func
。我不确定他们是否按照要求实现了
Equals
GetHashCode
,但您可以迭代该结构并创建自己的HashCode-但这并不像听起来那么简单。-我不确定这是否会带来性能上的好处。@Verarind然后我们可以从委托创建表达式树,但问题是,我们可以简单地比较两个表达式树对象的相等性吗?你的委托不一样。检查
delegate1.方法
。检查
delegate2.方法
。它们是两个不同的函数,恰好做同一件事。您询问委托的身份,但您的代码已经正确地检测到了这一点。“你真正感兴趣的是别的东西。”我之前说过的张翔:不确定。也许
Equals
被实现来处理具有相同内容的不同实例,但我想它不是。在这种情况下,您必须自己实现它。实际上,在第二个示例中,两个代理都将使用更新后的值
c
。这是一个好主意-但是类型转换将以字符串形式表示为
Convert(x)
,而不管转换为哪种类型。但在这种特殊情况下可能就足够了。啊,好主意,但是比较两个字符串可能会导致性能问题。如果有某个变量的clouser,那么它可能会失败。请参阅更新的问题,其中我编写了此测试的一个例外。嗯,那看起来很难。你可以使用顺序搜索来创建一个.startswith,至少可以减少字符串比较的性能损失(在字符串检查性能问题上为我创造了奇迹)@SWeko:用这种方法识别表达式时要小心
Func[int,int]“x=>2*x”
将被检测为与
Func[int,int]“z=>2*z”
不同,更重要的是
Func[double,double]“x=>2*x”
将被检测为匹配项。如果您只使用一种类型的委托,就可以了。但是,如果您打算扩大类型,这样的ID函数必须至少额外添加有关所有参数类型的信息,并且要100%安全,还必须包括所涉及的闭包类型。。。在某些情况下,您甚至可能需要有关所涉及程序集的信息。
expStr.OrderBy(ch=>ch.ToArray()
?@usr这一行“重置”表达式的顺序,例如
(a,b)=>((a+7)+5)+b)
(b,a)=>((7+b)+5)+a)
(相同的exp将两者转换为相同的表达式(id),类似于
((())++,ab57=>
@usr您是对的,更新了我的答案以涵盖此场景
public static string id(Expression<Func<int, int, int>> expression)
{
    return expression.ToString();
}
public class Funker
{
    private static Dictionary<string, string> _delegatesMapping;
    private static Dictionary<string, Func<int, int, int>> _delegates;
    public static Funker Instance { get; private set; }

    static Funker()
    {
        _delegatesMapping = new Dictionary<string, string>();
        _delegates = new Dictionary<string, Func<int, int, int>>();
        Instance = new Funker();
    }

    private Funker() { }

    public Func<int, int, int> this[Expression<Func<int, int, int>> del]
    {
        get
        {
            string expStr = del.ToString();

            var parameters = del.Parameters;

            for (int i = 0; i < parameters.Count; i++)
                expStr = expStr.Replace(parameters[i].Name, String.Concat('_' + i));

            Func<int, int, int> _Del = null;

            if (!_delegatesMapping.ContainsKey(expStr))
            {
                _Del = del.Compile();
                _delegatesMapping.Add(expStr, new String(expStr.OrderBy(ch => ch).ToArray()) + "|" + _Del(55, 77));
            }

            if (!_delegates.ContainsKey(_delegatesMapping[expStr])) _delegates.Add(_delegatesMapping[expStr], _Del ?? del.Compile());
            return _delegates[_delegatesMapping[expStr]];
        }
    }
}
class Program
{        

    static void Main(string[] args)
    {
        var funker = Funker.Instance;

        var del1 = funker[(a, b) => a + 71 + 12 + b];
        var del2 = funker[(hello, world) => 71 + hello + world + 12];
        var del3 = funker[(a, c) => a + 17 + 21 + c];

        Debug.Assert(del1 == del2);
        Debug.Assert(del1 == del3);
    }
}
true
false