7

我的很多类都是不同类型的知名但无序对象的容器,例如容器可能如下所示:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    public bool StoreIfKnown(object o)
    {
        // TODO...
    }
}

因此,如果o是类型A,则应将其存储在属性A中,键入属性等等。BB

在 F# 中,该StoreIfKnown方法可以编写如下(请原谅语法错误,我的 F# 不是很好而且很生锈):

match o with
| ?: A a -> A <- a; true
| ?: B b -> B <- b; true
| ?: C c -> C <- c; true
| _ -> false

但在 C# 中,唯一的方法似乎是相当冗长:

if (o is A)
{
    this.A = (A)o;
    return true;
}

if (o is B)
{
    this.B = (B)o;
    return true;
}

// etc.

return false;

我可以使用as关键字来避免测试/演员模式,这会更快,但更冗长。

在 C# 中有什么优雅的方法可以做到这一点吗?

4

5 回答 5

11

您可以在 'o' 和辅助类上编写扩展方法,以启用编程模型,例如

o.Match<A>( a => { this.A = a; return true; } )
 .Match<B>( b => { this.B = b; return true; } )
 .Else( () => { return false; } )

但是要小心不要在这里做太多类似 DSL 的骇客,以免你最终得到一个只有你理解的 API。

也可以看看

http://blogs.msdn.com/lucabol/archive/2008/07/15/ac-library-to-write-functional-code-part-v-the-match-operator.aspx

于 2009-10-02T12:17:47.073 回答
9

它不像 Brian 的解决方案那么漂亮,但这不需要定义新的 DSL。您会注意到您重复以下代码:

if (o is {DataType})
{
    {Property} = ({DataType})o;
    return true;
}

很容易将该模板拉入它自己的方法中,结果如下:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    private bool TestProp<T>(object o, Action<T> f)
    {
        if (o is T)
            return false;

        f((T)o);
        return true;
    }

    public bool StoreIfKnown(object o)
    {
        return
            TestProp<A>(o, x => A = x) ||
            TestProp<B>(o, x => B = x) ||
            TestProp<C>(o, x => C = x) ||
            false;
    }
}

如果您正在使用引用类型,则可以通过以下调整来利用类型推断:

    private bool TestProp<T>(T o, Action<T> f)
    {
        if (o == null)
            return false;

        f(o);
        return true;
    }

    public bool StoreIfKnown(object o)
    {
        return
            TestProp(o as A, x => A = x) ||
            TestProp(o as B, x => B = x) ||
            TestProp(o as C, x => C = x) ||
            false;
    }
于 2009-10-02T14:38:46.543 回答
8

我一直在玩一个小匹配构建器(受Brian 的回答启发),它允许类型检查、保护子句和从整个事情中返回结果。它使用类型推断,因此您唯一需要指定类型的地方就是您真正想要的地方。

所以,想象 typeC有一个IsActive我们想要的属性true,它看起来像这样:

var stored = Match.Against(o)
    .When<A>().Then(a => { this.A = a; return true; })
    .When<B>().Then(b => { this.B = b; return true; })
    .When<C>(c => c.IsActive).Then(c => { this.C = c; return true; })
    .Otherwise(a => false);

我认为这非常易读,特别是因为它允许在实际匹配之前针对派生类型运行谓词,这是我确实需要的。

代码很长,因为它需要在后台使用一些部分指定的构建器类来允许类型推断工作,所以我不能在这里发布它。但是,如果有人感兴趣,请在评论中告诉我,我会将其贴在我的博客上并在此处放置链接。

于 2009-10-02T15:40:41.187 回答
2

Bart de Smet 曾经对模式匹配很着迷,从这里开始(一直到第 8 部分)。如果您设法了解所有这些内容,那么在 C# 中的模式匹配应该不会有任何问题。如果有,stackoverflow 可能无法回答它们:)

于 2009-10-02T15:52:24.100 回答
1

截至 2016 年 8 月和 C# 7.0 预览版,对模式匹配的支持有限。您可以尝试使用Visual Studio “15” Preview 4

根据 MSDN 博客,您可以在两个地方使用模式:

  • is 表达式的右侧

  • 在switch 语句中的case 子句

可能的模式是:

  • c 形式的常量模式(其中 c 是 C# 中的常量表达式),用于测试输入是否等于 c

  • T x 形式的类型模式(其中 T 是一个类型,x 是一个标识符),它测试输入是否具有类型 T,如果是,则将输入的值提取到类型 T 的新变量 x 中

  • var x 形式的 var 模式(其中 x 是一个标识符),它始终匹配,并且只需将输入的值放入与输入具有相同类型的新变量 x 中

我没有安装 Visual Studio 15,所以我不确定我是否正确地重写了您的代码,但应该不会太远:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    public bool StoreIfKnown(object obj)
    {
        switch (obj)
        {
            case A a:
                this.A = a
                // I don't put "break" because I'm returning value from a method
                return true;
            case B b:
                this.B = b
                return true;
            case C c:
                this.C = c
                return true;
            default:
                WriteLine("<other>");
                return false;
        }
    }
}
于 2016-09-16T15:01:10.977 回答