我一直在研究一个小型数学脚本引擎(或DSL,如果你愿意的话)。让它变得有趣,它没什么大不了的。在任何情况下,我想要的功能之一是能够以类型安全的方式从中获取结果。问题是它可以返回5种不同的类型。
Number,bool,Fun,FunN和NamedValue。 AnyFun也是Fun和FunN的抽象基类。 Fun和FunN之间的区别在于Fun只接受一个参数,而FunN只接受一个参数。想象一下,通常只有一个参数可以保证一个单独的类型(可能是错误的)。
目前,我正在使用一个名为Result的包装类型和一个名为Matcher的类来实现这一目标(受到F#和Haskell等语言模式匹配的启发)。当你使用它时,它基本上就是这样。
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();
这是我目前的实施。但它很僵硬。添加新类型相当繁琐。
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);
}
}
}
问题是我想添加更多类型。我想让函数可以返回数字和bool,并将Fun改为Fun&lt; T>,其中T是返回类型。这实际上是主要问题所在。我有AnyFun,Fun,FunN,在介绍了这个改变后,我将不得不处理AnyFun,Fun&lt;数字&gt;,有趣&lt; bool&gt;,FunN&lt;数字&gt;,FunN&lt; bool&gt;。即便如此,我还是希望它能够将AnyFun与任何不匹配的功能相匹配。像这样:
engine.Eval(src).Match()
.Case((Fun<Number> result) => Console.WriteLine("I am special!!!"))
.Case((AnyFun result) => Console.WriteLine("I am a generic function"))
.Do();
有没有人有更好的实施建议,更好地处理新类型?或者有任何其他建议如何以类型安全的方式获得结果?另外,我应该为所有返回类型都有一个公共基类(并为bool添加一个新类型)吗?
性能不是问题,顺便说一句。
保重, 克尔
编辑:
阅读完反馈后,我改为创建了这个matcher类。
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();
}
}
它更短,但案件的顺序很重要。例如,在这种情况下,Fun选项将永远不会运行。
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
.Case((Fun result) => Console.WriteLine("I am alone"))
但是,如果你改变位置,它会。
.Case((Fun result) => Console.WriteLine("I am alone"))
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
有可能改善吗?我的代码还有其他问题吗?
编辑2:
解决了它:D。
答案 0 :(得分:2)
您的匹配器可以通过执行以下操作来处理无限类型:
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;
}
}
}
我认为您实际上并不需要列表,除非您的Result
之前没有Value
。我不太了解你的对象模型,但如果结果的类型是已知的,那么不要使用列表,只是立即进行类型测试。
答案 1 :(得分:0)
如果您总是希望以相同的方式处理结果(例如,如果您总是希望以相同的方式转换/调整特定类型的DSL对象),我建议您使用一个或多个字典来放置适配器委托like this
我不知道你打算如何扩展你的应用程序,但在我看来,在你的情况下,每个返回类型都有一个单独的字典,让它们都有零个或一个输入参数。 (而不是使用几个参数,只需将要返回的DSL参数包装到一个对象中)。
一个例子:
public class SomeClass
{
public IDictionary<Type, Action<object>> RegistryVoid { get; set; }
public IDictionary<Type, Func<object, int>> RegistryInt { get; set; }
public void SomeDlsMethod()
{
...
// Example when you need to convert your DSL data object to int:
int value = RegistryInt[someDslObject.GetType()](someDslObject);
}
}
如果您想在代码中以不同方式处理DSL结果,我建议使用TypeSwith found here。 TypeSwitch只是比使用多个if / else语句和转换更简单的方法。使用此方法,您可以指定使用它的逻辑,因此您不仅限于放入词典的逻辑。 (如果您愿意,可以轻松修改TypeSwitch以成为扩展方法。)
示例:
public class SomeClass
{
public void SomeDlsMethod()
{
TypeSwitch.Do(someDslObject,
TypeSwitch.Case<DslObjectA>(someDslObjectA => ...),
TypeSwitch.Case<DslObjectB>(someDslObjectB => ...),
TypeSwitch.Default(() => ...)
);
}
}