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