【问题标题】:Creating a type that can be used as a Func/method创建可用作 Func/method 的类型
【发布时间】:2013-03-12 01:37:12
【问题描述】:

我一直在和LINQ to Z3 玩耍(不是生产用途)。

我最终以这种语法作为开始:

var i = 123;
var test2 = from t in TheormProver.NewTheorm()
            let f = TheormProver.Func<int, bool>()
            let a = TheormProver.Int
            let g = TheormProver.Func<bool, int>()
            where !f(a * 2) && g(f(g(f(4)))) == i * a && a < g(f(a))
            select new { f = f.ToString(), g = g.ToString(), a, asd = "Test extra property" };

var solution = test2.Solve(); // Edited in for clarification
// note that test2 is a TheormProver<T> which has a "T Solve()" method defined.

静态的 TheromProver.IntTheormProver.Func 方法/属性当前仅返回一个基本类型(根据它们的名称)。

继续前进,我想创建一种 Variable&lt;T&gt; 类型,其中包含更多信息而不仅仅是一个值。

TL;DR: 我遇到的问题是我希望 fg 变量是我可以添加字段和属性的自定义类型,但是 我仍然希望能够将它们与我在 where 子句中的语法一起使用(即作为方法/Func)。

那么,如何在添加/拥有自己的属性的同时创建可用于方法语法的自定义类型?

请注意,我不在乎调用该方法是否什么都不做,或者不起作用,因为我将操纵 where 子句,因此它们永远不会被调用/执行。


示例:

var test2 = from t in TheormProver.NewTheorm()
            let f = TheormProver.Func<int, bool>()
            let a = TheormProver.Int
            where !f(a * 2) && a > 3 // f is used to create a method call expression
            select new { f , a };

var testSolution = test2.Solve();

var fSolution = testSolution.f; // F is its own type with unique properties/fields.

var fConstraints = fSolution.Constraints;

var fSomeProperty = fSolution.SomeProperty;

foreach(var constraint in fConstraints)
{
    //.....
}

到目前为止,我已经模拟了一个正在进行的语法的快速示例:

http://liveworkspace.org/code/3Fm6JM$0

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class TheormProver
    {
        public static int Int { get { return default(int); } } // Really this would return my Variable<int>
        public static Func<T, TResult> Func<T, TResult>() { return default(Func<T, TResult>); } // Really this would return my Variable<Func<T, TResult>>

        protected List<Expression> Constraints; // Holds constraints / where clauses that get translated into the Z3 language

        //This gets called when we do the first "let" and gets us into the correctly typed world with a generic parameter
        public virtual TheormProver<T> Select<T>(Func<TheormProver, T> sel)
        {
            return new TheormProver<T>(Constraints);
        }
    }

    // This is what the user of the library sees and is returned by a from t in new TheormProver(). T will be the anonymous type from the last let
    class TheormProver<T> : TheormProver
    {
        public TheormProver(List<Expression> Constraints)
        {

        }

        // This gets called on subsequent "let"s, going from the anonymous type with one property "f" to one with 2, "f, g". Chaining this way allows as many lets as we want
        public virtual TheormProver<U> Select<U>(Expression<Func<T, U>> sel)
        {
            return new TheormProver<T, U>(sel, Constraints.ToList());
        }

        public virtual TheormProver<T> Where(Expression<Func<T, bool>> constraint)
        {
            var result = (TheormProver<T>)this; // This should be a clone to allow composable queries

            result.Constraints.Add(constraint);

            return result;
        }

        public virtual T Solve(out bool foundSolution)
        {
            // TODO: Call Z3 and get a solution
            foundSolution = false;
            return default(T);
        }
    }

    internal class TheormProver<T, U> : TheormProver<U>
    {
        private LambdaExpression Selector;
        private TheormProver<T> InternalTheorumProver;

        public TheormProver(Expression<Func<T, U>> selector, List<Expression> constraints)
            : base(constraints)
        {
            Selector = selector;
            InternalTheorumProver = new TheormProver<T>(constraints);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var test = from t in new TheormProver()
                       let f = TheormProver.Func<int, bool>()
                       let g = TheormProver.Func<bool, int>()
                       let a = TheormProver.Int
                       where g(f(a)) == 0
                       select new { f, g, a };

            bool foundSolution;
            var testSolution = test.Solve(out foundSolution);
        }
    }
}

【问题讨论】:

  • 我认为如果你能展示一个你想要达到的目标的例子会很有帮助,即使那不是有效的语法。
  • 感谢您的浏览,请参阅我的编辑。
  • 为什么不创建一个具有这些属性和函数的类型呢? let f = new MyType(TheormProver.Func&lt;int, bool&gt;()) ... where !f.Func(a * 2)
  • 这可以作为替代方案,但理想情况下,我希望 where 子句尽可能简单。我喜欢能够做到f(g(a), g(123)) = 123 等的简单性。
  • 我想出的唯一其他选择是将f 保留为Func,但有一个扩展方法,他们将在选择中使用该方法将其转换为我的特殊类型为 select 语句获取正确的返回类型(类似于我当前使用 .ToString() 的语法)。

标签: c# linq types expression


【解决方案1】:

我为您的原始代码创建了一个简单的“测试平台”:http://liveworkspace.org/code/3Bl7wC$0

借助一点动态魔法,您可以使用以下类作为Func&lt;T1, T2&gt; 的替代品:

public class MyCallable<T1, T2> : DynamicObject
{
    private readonly Expression<Func<T1, T2> > _wrapped;
    private readonly Func<T1, T2> _compiled;

    public MyCallable(Expression<Func<T1, T2>> towrap) 
    { 
        _wrapped = towrap; _compiled = _wrapped.Compile(); 
    }

    public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
    {
        if ( (args.Length == 1) && 
             (args[0].GetType() == typeof(T1)))
        {
            Console.WriteLine(@"Invoking ""{0}"" on {1}", _wrapped, args[0]);
            result = _compiled((T1) args[0]);
            return true;
        }
        else
        {
            //throw new ArgumentException("Cannot invoke " + _wrapped + " with the arguments passed");
            result = null;
            return false;
        }
    }
}

如您所见,它将您的类定义为“动态的”,并允许您尝试调用它,就好像它是一个委托/函数/...一般可调用对象:

// in "TheormProver"
public static dynamic Func<T1, T2>() { return new MyCallable<T1, T2>(arg1 => default(T2)); }

这是有效的证明:http://liveworkspace.org/code/4kBypd$0

输出:

Invoking "arg1 => False" on 0
Invoking "arg1 => False" on 4
Invoking "arg1 => 0" on False
Invoking "arg1 => False" on 0
Invoking "arg1 => 0" on False
Invoking "arg1 => False" on 0
Invoking "arg1 => 0" on False

完整代码供参考:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Dynamic;

public class Program
{
    public class MyCallable<T1, T2> : DynamicObject
    {
        private readonly Expression<Func<T1, T2> > _wrapped;
        private readonly Func<T1, T2> _compiled;

        public MyCallable(Expression<Func<T1, T2>> towrap) 
        { 
            _wrapped = towrap; _compiled = _wrapped.Compile(); 
        }

        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
            if ( (args.Length == 1) && 
                 (args[0].GetType() == typeof(T1)))
            {
                Console.WriteLine(@"Invoking ""{0}"" on {1}", _wrapped, args[0]);
                result = _compiled((T1) args[0]);
                return true;
            }
            else
            {
                //throw new ArgumentException("Cannot invoke " + _wrapped + " with the arguments passed");
                result = null;
                return false;
            }
        }
    }

    public static class TheormProver
    {
        public static object[] NewTheorm() { return new object[] { 1 }; }
        public static dynamic Func<T1, T2>() { return new MyCallable<T1, T2>(arg1 => default(T2)); }
        public static int Int { get; set; }
    }

    public static void Main(string[] args)
    {
        var i = 123;
        var test2 = from t in TheormProver.NewTheorm()
            let f = TheormProver.Func<int, bool>()
            let a = TheormProver.Int
            let g = TheormProver.Func<bool, int>()
            where !f(a * 2) && g(f(g(f(4)))) == i * a && a < g(f(a))
            select new { f = f.ToString(), g = g.ToString(), a, asd = "Test extra property" };

        test2.ToList().ForEach(Console.WriteLine);
    }

}

【讨论】:

  • 非常好的主意。它是否作为表达式树的一部分工作?我对动态不太熟悉。
  • 确实是一个很酷的想法。我什至不知道动态对象的行为可以像委托一样。但我看到了与 usr 相同的问题:它很可能在表达式树中无法正常工作,因为整个代码只在运行时进行评估。
  • @usr 我不确定“作为表达式树的一部分”是什么意思。一般来说,答案是“是”:这只是 DLR 的正常使用。但是,如果您的意思是,在表达式级别是否是强类型,答案是否定的。这是“动态”的本质:它是后期绑定的,但是您可以充分利用适当的重载解析、扩展方法解析、用户定义的转换和其他在“正常”C# 泛型中不起作用的东西。
  • 据我了解,OP 正在挑选 IQueryable 他生成 Z3 术语。在这种情况下,他在处理动态时需要同样的能力。不确定您是否可以将动态用作Expression 的一部分。
  • @DanielHilgarth 我同意。不过,您提到的限制显然不是问题中的要求。这个问题完全集中在“函数调用”语法兼容性上,AFAICS
【解决方案2】:

您不能将自定义成员添加到委托类型,也不能在 C# 中重载 operator ()。这给你留下了扩展方法。

现在,您不想为非常通用的委托类型(如 Func&lt;int, int&gt;)添加扩展,因为这会污染命名空间。我建议您像这样创建自定义委托:

delegate TResult Z3Func<T1, TResult>(T1 arg1);

然后你可以给Z3Func添加扩展名。

扩展调用最终将作为您正在分析的表达式树中的静态方法调用。

【讨论】:

  • 只要通用Func上的扩展方法不在一个常用的命名空间中,我认为智能感知污染问题不大。
  • 为什么这么快就被接受了?我仍在写一个实际上适用于未修改语法的答案。等 5 分钟
  • @sehe:现在你让我感兴趣了。
  • @DanielHilgarth 刚刚发布了
  • @sehe:不接受。我会将它打开一两天,以便有更多机会获得更好的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多