C#:在代码中找出算术运算的结果类型(例如int + double = double)

时间:2017-08-03 13:56:23

标签: c# reflection

我正在尝试提出一个函数来确定算术运算的结果类型,比如添加的情况:

Type TypeOfAddition(Type leftType, Type rightType)
{
  // ???
}

Type TypeOfMultiplication(Type leftType, Type rightType)
{
  // ???
}

// ... same for subtraction and division

这些功能的期望结果可能很清楚;从本质上讲,我的目标是在执行算术运算时,在将类型推断为“var”类型变量时Visual Studio所做的相同(在运行时)。

例如,

public class MyClass
{
    public static string operator +(MyClass left, double right)
    {
        // ...
    }
}

TypeOfAddition(typeof(int), typeof(double)); // Should return typeof(double)
TypeOfAddition(typeof(string), typeof(int)); // Should return typeof(string)
TypeOfAddition(typeof(MyClass), typeof(double));  // Should return typeof(string)

我的基本想法是概念性的实施

Type TypeOfAddition(Type leftType, Type rightType)
{
  return leftType.GetMethods().Single(x =>
    x.Name == "op_Addition" &&
    x.GetParamters().Count == 2 &&
    x.GetParameters().Last().ParameterType == rightType);
}

A)这对基本类型(如int,double等)不起作用,它们似乎没有明确定义运算符重载,并且

B)上述linq条款尚未捕获所有案例(例如继承)

我可以对基本类型进行硬编码,并试图为B)提供一个智能解决方案,但这似乎相对......不优雅。

有没有更聪明/更容易/更好的解决方案来解决这个问题? 请注意,我只想获得这种操作结果的理论类型,而不是显式执行算术运算。

谢谢!

3 个答案:

答案 0 :(得分:3)

它当然不漂亮,绝对不是很快,但它似乎适用于我经历过的基本测试。

请注意,您需要引用Microsoft.CSharp.dll

Type TypeOfAddition<TLeft, TRight>()
{
    object GetDefault<T>()
    {
        if (typeof(T).IsValueType)
        {
            return default(T);
        }

        if (typeof(T) == typeof(string))
        {
            return string.Empty;
        }

        return (T)FormatterServices.GetUninitializedObject(typeof(T));
    }

    var binder = Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(
        CSharpBinderFlags.None,
        ExpressionType.Add,
        null,
        new CSharpArgumentInfo[] {
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
        }
    );

    var left = Expression.Parameter(typeof(TLeft));
    var right = Expression.Parameter(typeof(TRight));

    var func = Expression.Lambda(
        Expression.Dynamic(binder, typeof(object), left, right),
        new[] { left, right }
    ).Compile();

    return func
        .DynamicInvoke(GetDefault<TLeft>(), GetDefault<TRight>())
        ?.GetType() ?? typeof(object);
}

示例输出:

public class MyClass
{
    public static string operator +(MyClass left, double right)
    {
        return "";
    }
}

TypeOfAddition<string, int>().Dump();     // System.String
TypeOfAddition<int, double>().Dump();     // System.Double
TypeOfAddition<float, double>().Dump();   // System.Double
TypeOfAddition<MyClass, double>().Dump(); // System.String

这使用Jeroen在评论(RuntimeBinder)中提到的内容来创建附加活页夹。然后,它会构建一个动态表达式树,以添加TLeftTRight的默认值。我必须添加一个名为GetDefault的小函数来将string解析为空字符串,因为我假设您在尝试添加string而不是"" + 0时希望看到null GetDefault。如果您执行想要查看空值,只需将default(TLeft)来电替换为default(TRight) $query = $analytics->data_ga->get('ga:' . $ga_profile_id, $start_date, $end_date, 'ga:sessions', array('max-results' => 10000)); $query = $analytics->data_ga->get('ga:' . $ga_profile_id, $start_date, $end_date, 'ga:visitBounceRate', array('max-results' => 1000, 'dimensions' => 'ga:channelGrouping',)); if ($query['totalResults'] != 0 && isset($query['rows'])) { $bounce_rate_total = round($query['totalsForAllResults']['ga:visitBounceRate'],2); print 'Bounce Rate Total: '.$bounce_rate_total.'<br/>'; $bounce_rate_organic = round($query['rows'][2][1],2); print 'Bounce Rate organic: '.$bounce_rate_organic.'<br/>'; $bounce_rate_ppc = round($query['rows'][3][1],2); print 'Bounce Rate PPC: '.$bounce_rate_ppc.'<br/>'; $bounce_rate_social = round($query['rows'][5][1],2); print 'Bounce Rate Social: '.$bounce_rate_social.'<br/>'; }

它不调用构造函数(由于使用GetUninitializedObject)包含字符串的特殊情况。

可能有很多可能的改进,我很满意。

答案 1 :(得分:0)

使用Roslyn,我现在想出了以下内容。到目前为止,我测试的似乎工作正常 - 让我知道你的想法。

从我看到的内容(虽然我的情况下没有重大问题)

  • 有点慢,至少在第一次通话时
  • 必须显式引用程序集。不确定是否会将这些触发器重新加载到Roslyn脚本使用的任何AppDomain / context中,如果是这样,可能会使许多/大型程序集的速度进一步降低
  • 显然需要为我的应用程序使用/部署很多Roslyn程序集

    static async Task<Type> GetOperationResultTypeAsync(Type left, Type right, string operatorSymbol)
    {
        // Reference all assemblies that are loaded in the current AppDomain (plugins?)
        var options = ScriptOptions.Default.AddReferences(AppDomain.CurrentDomain.GetAssemblies());
        var script = CSharpScript.Create($"var instance = default({left.FullName}) {operatorSymbol} default({right.FullName});", options: options);
    
        var compilation = script.GetCompilation();
        var syntaxTree = compilation.SyntaxTrees.Single();
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
    
        var variableDeclaration = (await syntaxTree.GetRootAsync())
            .DescendantNodes()
            .OfType<VariableDeclarationSyntax>()
            .Single();
    
        var symbolInfo = semanticModel.GetSymbolInfo(variableDeclaration.Type);
        var typeSymbol = (ITypeSymbol)symbolInfo.Symbol; // will be null on error (eg operation not possible/defined/allowed)
    
        if (typeSymbol == null)
            return null;
    
        var symbolDisplayFormat = new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
        string fullyQualifiedName = typeSymbol.ToDisplayString(symbolDisplayFormat);
    
        Type type = Type.GetType(fullyQualifiedName, throwOnError: true);
        return type;
    }
    

用法只是

Type t1 = await GetOperationResultTypeAsync(typeof(MyClass), typeof(double), "+");
Type t2 = await GetOperationResultTypeAsync(typeof(int), typeof(int), "+");
Type t3 = await GetOperationResultTypeAsync(typeof(int), typeof(double), "+");

答案 2 :(得分:-1)

也许你可以在你的方法中尝试泛型。像

这样的东西
Type TypeOfAddition<T, T2>(T leftNum, T2 rightNum){
    var result = leftNum + rightNum;
    return typeof(result);
}

Type TypeOfMultiplication<T, T2>(T leftNum, T2 rightNum){
    var result = leftNum * rightNum;
    return typeof(result);
}