类型安全的方式来从C#脚本语言返回值 [英] Type safe way to return values from a scripting language in C#

查看:93
本文介绍了类型安全的方式来从C#脚本语言返回值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在一个小的数学脚本引擎(或DSL,如果你喜欢)。使得它的乐趣,它并不严重。在任何情况下,我想的特征之一是,以获得在一个类型安全的方式从它的结果的能力。问题是,有5种不同类型,它可以返回。



数字,布尔,娱乐,FunN和的NamedValue。还有AnyFun这是乐趣和FunN一个抽象基类。乐趣和FunN之间的区别在于玩转只需要一个参数,而FunN时间超过一个参数。想通这是很常见的一个参数就保证一个单独的类型(可能是错的)。



目前,我使用所谓的结果的包装类型,一类叫匹配做到这一点(通过模式匹配像F#和Haskell语言的启发)。它基本上是这样的,当你使用它。

  engine.Eval(SRC).Match()
。病例((数字结果)=> Console.WriteLine(我是一个数字))
。病例((布尔结果)=> Console.WriteLine(我是一个布尔值))
。病例((FUN结果)=> Console.WriteLine(我是一个参数的函数))
。病例((AnyFun结果)=> Console.WriteLine(我的任何函数,那不是玩转))
。做();

这是我目前的执行情况。它是刚性的,但。 。添加新类型是相当繁琐的。



 公共类结果
{
公共对象的Val {获得;私人集; }
私人回调<&匹配器GT; _finishMatch {搞定;私人集; }

公开结果(数字VAL)
{
瓦尔= VAL;
_finishMatch =(M)=> m.OnNum(VAL);
}

公开结果(布尔VAL)
{
瓦尔= VAL;
_finishMatch =(M)=> m.OnBool(VAL);
}

...为其他结果类型构造更...

公共匹配器匹配()
{
返回新匹配(本);
}

//用于匹配的结果
公共类匹配器
{
的内部回调<数字与GT; OnNum {搞定;私人集; }
的内部回调<布尔> OnBool {搞定;私人集; }
的内部回调<&的NamedValue GT; OnNamed {搞定;私人集; }
的内部回调< AnyFun> OnAnyFun {搞定;私人集; }
的内部回调<娱乐与GT; OnFun {搞定;私人集; }
的内部回调< FunN> OnFunN {搞定;私人集; }
的内部回调<对象> OnElse {搞定;私人集; }
私人结果_result;

公共匹配器(结果R)
{
OnElse =(忽略)=>
{
抛出新的异常(必须添加一个新的异常,这个......但有此无一例:P);
};
OnNum =(VAL)=> OnElse(VAL);
OnBool =(VAL)=> OnElse(VAL);
OnNamed =(VAL)=> OnElse(VAL);
OnAnyFun =(VAL)=> OnElse(VAL);
OnFun =(VAL)=> OnAnyFun(VAL);
OnFunN =(VAL)=> OnAnyFun(VAL);
_result = R;
}

公共匹配器外壳(回拨<数字与GT; FN)
{
OnNum = FN;
返回这一点;
}

公共匹配器外壳(回拨<布尔> FN)
{
OnBool = FN;
返回这一点;
}

...案例方法的返回类型休息...

公共无效DO()
{
_result._finishMatch(本);
}
}
}



的事情是,我想添加更多的类型。我要赚那么函数可以返回数字和布尔变量,改变玩转娱乐与LT; T>,其中T是返回类型。这实际上是主要问题所在。我有AnyFun,娱乐,FunN,并引入这种变化之后,我将不得不应对AnyFun,娱乐与LT;号码>,娱乐与LT;布尔>,FunN<号码>,FunN<布尔>。而且当时我希望它针对心不是匹配任何自己匹配功能AnyFun。像这样的:

  engine.Eval(SRC).Match()
。病例((FUN<数>的结果)=> Console.WriteLine(我是特殊的!))
。病例((AnyFun结果)=> Console.WriteLine(我是一个泛型函数))
。做();



有没有人有一个更好的实施任何建议,处理添加新类型好?还是有关于如何得到的结果在一个类型安全的方式任何其他建议?另外,我应该对所有的返回类型的通用基类(并添加布尔新型)?



性能不是问题,顺便说一句。



保重,
克尔



编辑:



阅读反馈之后,我创造了这个匹配类代替。

 公共类匹配器
{
私人行动_onCase;
私人结果_result;

公共匹配器(结果R)
{
_onCase = NULL;
_result = R;
}

公共匹配器外壳< T>(回拨< T> FN)如果
{
(_result.Val是T&放大器;&安培; _onCase == NULL )
{
_onCase =()=> FN((T)_result.Val);
}
返回这一点;
}

公共无效品(回拨<对象> FN)
{
如果(_onCase!= NULL)
_onCase();
,否则
FN(_result.Val);
}

公共无效DO()
{
如果(_onCase == NULL)
抛出新的异常(必须添加一个新的异常此......但有此无一例:P);
_onCase();
}
}



其短,但案件的秩序问题。例如,在这种情况下,有趣的选项将永远运行

 。病例((AnyFun结果)=> Console.WriteLine (AAANNNNNNNYYYYYYYYYYYYY !!!!))
。病例((FUN结果)=> Console.WriteLine(我是孤单))

但它如果切换的地方。

 。病例((有趣的结果)=> Console.WriteLine(我是孤单))
。病例((AnyFun结果)=> Console.WriteLine(AAANNNNNNNYYYYYYYYYYYYY !!!!))

是否有可能提高呢? ?有我的代码的任何其他问题。



编辑2:



解决它。:D



<$ p $:>
解决方案

您匹配可以做这样的事情处理无限类型p> 公共类匹配器
{
私人只读结果的结果; //在
私人只读名单,LT通过此; Func键<结果,布尔>>案件=新...();

公共匹配器外壳< T>(动作< T>动作)
{
cases.add(结果=>
{
如果(typeof运算(T).IsAssignableFrom(result.Value.GetType()))
{
动作((T)(result.Value));
返回真;
}
返回FALSE;
}
返回本;
}

公共无效DO()
{
每一个(VAR @case在例)
{
如果(@case(结果))回报;
}
}
}

我想你实际上并不需要的列表,除非你的结果不具有直到后来,我不太明白你的对象模型,但如果结果的类型是已知的,那么就不要使用名单,只是立即做型式试验


I have been working on a small mathematical scripting engine (or DSL, if you prefer). Making it for fun, its nothing serious. In any case, one of the features I want is the ability to get results from it in a type safe manner. The problem is that there are 5 different types that it can return.

Number, bool, Fun, FunN and NamedValue. There is also AnyFun which is a abstract base class for Fun and FunN. The difference between Fun and FunN is that Fun only takes one argument, while FunN takes more then one argument. Figured it was common enough with one argument to warrant a separate type (could be wrong).

At the moment, I am using a wrapper type called Result and a class called Matcher to accomplish this (inspired by pattern matching in languages like F# and Haskell). It basically looks like this when you use it.

engine.Eval(src).Match()
  .Case((Number result) => Console.WriteLine("I am a number"))
  .Case((bool result) => Console.WriteLine("I am a bool"))
  .Case((Fun result) => Console.WriteLine("I am a function with one argument"))
  .Case((AnyFun result) => Console.WriteLine("I am any function thats not Fun"))
  .Do();

This is my current implementation. It is rigid, though. Adding new types is rather tedious.

public class Result
{
    public object Val { get; private set; }
    private Callback<Matcher> _finishMatch { get; private set; }

    public Result(Number val)
    {
        Val = val;
        _finishMatch = (m) => m.OnNum(val);
    }

    public Result(bool val)
    {
        Val = val;
        _finishMatch = (m) => m.OnBool(val);
    }

    ... more constructors for the other result types ...

    public Matcher Match()
    {
        return new Matcher(this);
    }

    // Used to match a result
    public class Matcher
    {
        internal Callback<Number> OnNum { get; private set; }
        internal Callback<bool> OnBool { get; private set; }
        internal Callback<NamedValue> OnNamed { get; private set; }
        internal Callback<AnyFun> OnAnyFun { get; private set; }
        internal Callback<Fun> OnFun { get; private set; }
        internal Callback<FunN> OnFunN { get; private set; }
        internal Callback<object> OnElse { get; private set; }
        private Result _result;

        public Matcher(Result r)
        {
            OnElse = (ignored) =>
            {
                throw new Exception("Must add a new exception for this... but there was no case for this :P");
            };
            OnNum = (val) => OnElse(val);
            OnBool = (val) => OnElse(val);
            OnNamed = (val) => OnElse(val);
            OnAnyFun = (val) => OnElse(val);
            OnFun = (val) => OnAnyFun(val);
            OnFunN = (val) => OnAnyFun(val);
            _result = r;
        }

        public Matcher Case(Callback<Number> fn)
        {
            OnNum = fn;
            return this;
        }

        public Matcher Case(Callback<bool> fn)
        {
            OnBool = fn;
            return this;
        }

        ... Case methods for the rest of the return types ...

        public void Do()
        {
            _result._finishMatch(this);
        }
    }
}

The thing is that I want to add more types. I want to make so functions can return both numbers and bools, and change Fun to Fun< T >, where T is the return type. This is actually where the main problem lies. I have AnyFun, Fun, FunN, and after introducing this change I would have to deal with AnyFun, Fun< Number >, Fun< bool >, FunN< Number >, FunN< bool >. And even then I would want it to match AnyFun against any function that isnt matched themselves. Like this:

engine.Eval(src).Match()
  .Case((Fun<Number> result) => Console.WriteLine("I am special!!!"))
  .Case((AnyFun result) => Console.WriteLine("I am a generic function"))
  .Do();

Does anyone have any suggestions for a better implementation, that handles adding new types better? Or are there any other suggestions for how to get the result in a type safe manner? Also, should I have a common base class for all the return types (and add a new type for bool)?

Performance is not an issue, btw.

Take care, Kerr

EDIT:

After reading the feedback, I have created this matcher class instead.

public class Matcher
{
    private Action _onCase;
    private Result _result;

    public Matcher(Result r)
    {
        _onCase = null;
        _result = r;
    }

    public Matcher Case<T>(Callback<T> fn)
    {
        if (_result.Val is T && _onCase == null)
        {
            _onCase = () => fn((T)_result.Val);
        }
        return this;
    }

    public void Else(Callback<object> fn)
    {
        if (_onCase != null)
            _onCase();
        else
            fn(_result.Val);
    }

    public void Do()
    {
        if (_onCase == null)
            throw new Exception("Must add a new exception for this... but there was no case for this :P");
        _onCase();
    }
}

Its shorter, but the order of the cases matter. For example, in this case the Fun option will never run.

.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
.Case((Fun result) => Console.WriteLine("I am alone"))

But it will if you switch places.

.Case((Fun result) => Console.WriteLine("I am alone"))
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))

Is it possible to improve that? Are there any other issues with my code?

EDIT 2:

Solved it :D.

解决方案

Your matcher could handle unlimited types by doing something like this:

public class Matcher
{
    private readonly Result result; // pass this in
    private readonly List<Func<Result, bool>> cases = new ...();

    public Matcher Case<T>(Action<T> action)
    {
        cases.add(result =>
        {
            if(typeof(T).IsAssignableFrom(result.Value.GetType()))
            {
                action((T)(result.Value));
                return true;
            }
            return false;
        }
        return this;
    }

    public void Do()
    {
        for each(var @case in cases)
        {
            if(@case(result)) return;
        }
    }
}

I think you don't actually need a list, unless your Result doesn't have a Value until later on. I don't quite understand your object model, but if the type of the result is known, then don't use a list and just do the type test immediately.

这篇关于类型安全的方式来从C#脚本语言返回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆