Ближайший C# эквивалент выражения F#?

Я нахожусь в ситуации, когда многие мои классы представляют собой контейнеры с хорошо известными, но неупорядоченными объектами разных типов, например, контейнер может выглядеть следующим образом:

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 свойство, тип B в B собственность и тд.

В 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 ключевое слово, чтобы избежать шаблона test / cast, который был бы быстрее, но еще более подробным.

Есть ли какой-нибудь элегантный способ сделать это в C#?

5 ответов

Решение

Вы можете создать метод расширения для классов 'o' и helper для включения такой модели программирования, как

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/a-c-library-to-write-functional-code-part-v-the-match-operator.aspx

Это не так хорошо, как решение Брайана, но это не требует определения нового 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;
    }

Я играл с маленьким строителем спичек (вдохновленный ответом Брайана), который позволяет проверять типы, охранные предложения и возвращать результат из всего этого. Он использует вывод типа, поэтому единственное место, где вам нужно указать тип, это то, где вы действительно хотите.

Итак, воображая тип C имеет 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);

То, что я считаю довольно читабельным, тем более, что оно позволяет предикату запускаться с производным типом до фактического сопоставления, что мне действительно нужно.

Код довольно длинный, так как ему нужно несколько частично определенных классов построителя в фоновом режиме, чтобы разрешить вывод типов, поэтому я не могу опубликовать его здесь. Но если кому-то будет интересно, дайте мне знать в комментариях, и я выложу его в своем блоге и поставлю здесь ссылку.

Барт де Смет однажды сошел с ума от сопоставления с образцом, начиная здесь (вплоть до части 8). Если вам когда-нибудь удастся пройти через весь этот контент, не должно быть никаких вопросов по сопоставлению с образцом в C#. Если есть, то, вероятно, на них не может ответить stackru:)

По состоянию на август 2016 года и предварительный просмотр C# 7.0, поддержка сопоставления с образцом ограничена. Вы можете попробовать, если с помощью Visual Studio "15" Preview 4.

Согласно блогу MSDN, вы можете использовать шаблоны в двух местах:

  • на правой стороне выражения

  • в кейсах в выражениях switch

Возможные модели:

  • Шаблоны констант в форме c (где c - константное выражение в C#), которые проверяют, что входное значение равно c

  • Типовые шаблоны в форме T x (где T - тип, а x - идентификатор), который проверяет, что вход имеет тип T, и, если это так, извлекает значение ввода в новую переменную x типа T

  • Шаблоны var вида var x (где 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;
        }
    }
}
Другие вопросы по тегам