Pohon ekspresi dalam C # menggunakan contoh menemukan turunan (Pengunjung Pohon Ekspresi vs Pencocokan Pola)

Selamat siang. Pohon ekspresi, terutama bila dikombinasikan dengan pola Pengunjung, selalu menjadi topik yang agak membingungkan. Oleh karena itu, semakin beragam informasi tentang topik ini, semakin banyak contoh, semakin mudah bagi mereka yang tertarik untuk menemukan sesuatu yang jelas dan bermanfaat bagi mereka.



Artikel ini dibangun seperti biasa - dimulai dengan kerangka kerja konseptual dan definisi dan diakhiri dengan contoh dan cara penggunaan. Daftar isi di bawah ini.

Dasar-dasar Pohon Ekspresi
Sintaksis Pohon Ekspresi
Jenis Ekspresi
Pola Pencocokan
Pengunjung Naif Pengunjung
Klasik

Nah, tujuannya bukan untuk memaksakan solusi tertentu atau mengatakan bahwa satu lebih baik daripada yang lain. Saya mengusulkan untuk menarik kesimpulan sendiri, dengan mempertimbangkan semua nuansa dalam kasus Anda. Saya akan mengungkapkan pendapat saya pada contoh saya.


Pohon Ekspresi


Dasar


Pertama, Anda perlu berurusan dengan pohon ekspresi. Mereka berarti jenis Ekspresi atau ahli warisnya (mereka akan dibahas nanti). Dalam skenario biasa, ekspresi / algoritme disajikan dalam bentuk kode / instruksi yang dapat dieksekusi di mana pengguna mungkin tidak memiliki banyak hal untuk dilakukan (terutama eksekusi). Tipe Ekspresi memungkinkan Anda untuk mewakili ekspresi / algoritma (biasanya lambdas, tetapi tidak perlu) sebagai data yang diorganisasikan dalam struktur pohon yang dapat diakses oleh pengguna. Cara seperti pohon mengatur informasi tentang algoritma dan nama kelas memberi kita "pohon ekspresi".

Untuk kejelasan, kami akan menganalisis contoh sederhana. Misalkan kita memiliki lambda

(x) => Console.WriteLine (x + 5)

Ini dapat direpresentasikan sebagai pohon berikut


Akar pohon adalah bagian atas MethodCall , parameter dari metode ini juga ekspresi, oleh karena itu dapat memiliki jumlah anak.

Dalam kasus kami, hanya ada satu keturunan - puncak " ArithmeticOperation ". Ini berisi informasi tentang operasi apa itu dan operan kiri dan kanan juga ekspresi. Vertex seperti itu akan selalu memiliki 2 keturunan.

Operan diwakili oleh konstanta ( Konstan ) dan parameter ( Parameter ). Ekspresi semacam itu tidak memiliki keturunan.

Ini adalah contoh yang sangat sederhana, tetapi sepenuhnya mencerminkan esensi.

Fitur utama pohon ekspresi adalah mereka dapat diuraikan dan membaca semua informasi yang diperlukan tentang apa yang harus dilakukan algoritma. Dari beberapa sudut pandang, ini adalah kebalikan dari atribut. Atribut adalah sarana deskripsi perilaku deklaratif (sangat kondisional, tetapi tujuan akhir kira-kira sama). Sedangkan pohon ekspresi menggunakan fungsi / algoritma untuk menggambarkan data.

Mereka digunakan, misalnya, dalam penyedia kerangka kerja entitas. Aplikasi ini jelas - untuk mengurai pohon ekspresi, untuk memahami apa yang harus dieksekusi di sana dan membuat SQL dari deskripsi ini . Contoh yang kurang terkenal adalah perpustakaan moq untuk diolok-olok . Pohon ekspresi juga digunakan dalam DLR.(runtime bahasa dinamis). Pengembang kompiler menggunakannya untuk memastikan kompatibilitas antara sifat dinamis dan dotnet, alih-alih menghasilkan MSIL .

Perlu juga disebutkan bahwa pohon ekspresi tidak dapat diubah.

Sintaksis


Hal berikutnya yang layak dibahas adalah sintaksisnya. Ada 2 cara utama:

  • Membuat pohon ekspresi melalui metode statis dari kelas Ekspresi
  • Menggunakan ekspresi lambda yang dikompilasi dalam Ekspresi

Metode Statis dari Kelas Ekspresi


Membuat pohon ekspresi melalui metode statis dari kelas Ekspresi lebih jarang digunakan (terutama dari sudut pandang pengguna). Ini rumit, tetapi cukup sederhana, kami memiliki
banyak batu bata dasar yang dapat Anda gunakan, dari mana Anda dapat membangun
hal-hal yang cukup rumit . Penciptaan terjadi melalui metode statis sejak itu konstruktor ekspresi memiliki pengubah internal . Dan ini tidak berarti bahwa Anda perlu mengungkap refleksi.

Sebagai contoh, saya akan membuat ekspresi dari contoh di atas:

(x) => Console.WriteLine (x + 5)

ParameterExpression parameter = Expression.Parameter(typeof(double));
ConstantExpression constant = Expression.Constant(5d, typeof(double));
BinaryExpression add = Expression.Add(parameter, constant);
MethodInfo writeLine = typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(double) });
MethodCallExpression methodCall = Expression.Call(null, writeLine, add);
Expression<Action<double>> expressionlambda = Expression.Lambda<Action<double>>(methodCall, parameter);
Action<double> delegateLambda = expressionlambda.Compile();
delegateLambda(123321);

Ini mungkin bukan cara yang mudah, tetapi sepenuhnya mencerminkan struktur internal pohon ekspresi. Plus, metode ini menyediakan lebih banyak fitur dan fitur yang dapat digunakan dalam pohon ekspresi: dari loop, kondisi, try-catch, goto, tugas, diakhiri dengan blok kesalahan, informasi debugging untuk breakpoints, dinamis, dll.

Ekspresi lambda


Menggunakan lambdas sebagai ungkapan lebih sering dilakukan. Ini bekerja sangat sederhana - kompiler pintar pada tahap kompilasi melihat apa yang digunakan lambda. Dan mengkompilasinya menjadi delegasi atau ekspresi. Pada contoh yang sudah dikuasai, tampilannya sebagai berikut

Expression<Action<double>> write =  => Console.WriteLine( + 5);

Perlu mengklarifikasi hal semacam itu - sebuah ekspresi adalah deskripsi yang lengkap. Dan itu sudah cukup untuk
mendapatkan hasilnya. Pohon ekspresi seperti LambdaExpression atau turunannya dapat
dikonversi menjadi IL yang dapat dieksekusi. Jenis yang tersisa tidak dapat secara langsung dikonversi ke kode yang dapat dieksekusi (tapi ini tidak masuk akal).
By the way, jika seseorang sangat penting dari kompilasi cepat dari sebuah ekspresi, Anda bisa lihat ini proyek pihak ketiga.

Kebalikannya tidak benar dalam kasus umum. Seorang delegasi tidak bisa begitu saja mengambilnya dan memperkenalkan dirinya sebagai sebuah ekspresi (tetapi ini masih mungkin).

Tidak semua lambda dapat dikonversi menjadi pohon ekspresi. Ini termasuk:

  • Berisi operator penugasan
  • Menyumbang dinamis
  • Tidak sinkron
  • Dengan tubuh (kawat gigi)

double variable;
dynamic dynamic;
Expression<Action> assignment = () => variable = 5; //Compiler error: An expression tree may not contain an assignment operator
Expression<Func<double>> dynamically = () => dynamic; //Compiler error: An expression tree may not contain a dynamic operation
Expression<Func<Task>> asynchon = async () => await Task.CompletedTask; //Compiler error: Async lambda cannot be converted to expresiion trees
Expression<Action> body = () => { }; //Compiler error: A lambda expression with a statement body cannot be converted to an expression tree


Jenis-jenis Ekspresi


Saya sarankan untuk melihat sekilas jenis yang tersedia untuk mewakili peluang apa yang kita miliki. Semuanya ada dalam namespace System.Linq.Expressions.Saya

sarankan Anda untuk berkenalan dengan beberapa fitur yang sangat menarik dan tidak biasa. Jenis ekspresi yang lebih sederhana yang saya kumpulkan di tablet dengan deskripsi singkat.

Dinamis


Menggunakan DynamicExpression dimungkinkan untuk menggunakan dinamis dan semua fitur-fiturnya di pohon ekspresi. Ada API yang agak membingungkan, saya duduk di contoh ini lebih lama dari pada yang lainnya digabungkan. Semua kebingungan disediakan oleh banyak bendera. Dan beberapa di antaranya mirip dengan yang Anda cari, tetapi belum tentu. Dan ketika bekerja dengan dinamis dalam pohon ekspresi, sulit untuk mendapatkan kesalahan bicara. Contoh:

var parameter1 = Expression.Parameter(typeof(object), "name1");
var parameter2 = Expression.Parameter(typeof(object), "name2"); 
var dynamicParam1 = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
var dynamicParam2 = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
CallSiteBinder csb = Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.Add, typeof(Program), new[] { dynamicParam1, dynamicParam2 });
var dyno = Expression.Dynamic(csb, typeof(object), parameter1, parameter2);
Expression<Func<dynamic, dynamic, dynamic>> expr = Expression.Lambda<Func<dynamic, dynamic, dynamic>>(dyno, new[] { parameter1, parameter2 });
Func<dynamic, dynamic, dynamic> action = expr.Compile();
var res = action("1", "2");
Console.WriteLine(res); //12
res = action(1, 2);
Console.WriteLine(res); //3

Saya secara eksplisit menunjukkan dari mana Binder berasal untuk menghindari kebingungan dengan binder dari System.Reflection. Dari hal-hal menarik, kita dapat melakukan parameter ref dan out, parameter bernama, operasi unary, dan pada prinsipnya, semua yang dapat dilakukan melalui dinamis, tetapi ini akan memerlukan beberapa keterampilan.

Blok tangkapan pengecualian


Hal kedua yang akan saya perhatikan adalah fungsi try / catch / akhirnya / fault, atau lebih tepatnya, fakta
bahwa kita memiliki akses ke blok kesalahan. Ini tidak tersedia dalam C #, tetapi dalam MSIL. Ini adalah semacam
analog akhirnya yang akan dieksekusi jika ada pengecualian. Pada contoh di bawah ini, pengecualian akan dilemparkan, setelah itu "Hai" akan ditampilkan dan program akan menunggu input. Hanya setelah itu akan jatuh sepenuhnya. Saya tidak merekomendasikan praktik ini untuk digunakan.

var throwSmth = Expression.Throw(Expression.Constant(new Exception(), typeof(Exception)));
var log = Expression.Call(null, typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(string) }), Expression.Constant("Hi", typeof(string)));
var read = Expression.Call(null, typeof(Console).GetMethod(nameof(Console.ReadLine)));
var fault = Expression.TryFault(throwSmth, Expression.Block(new[] { log, read }));
Expression<Action> expr = Expression.Lambda<Action>(fault);
Action compiledExpression = expr.Compile();
compiledExpression();

Deskripsi Singkat Jenis Pohon Ekspresi yang Tersedia

Meja


Sebuah tipeDeskripsi Singkat
Utama
Ekspresi, . ,
Expression<TDelegate>
BinaryExpression(+, β€” )
UnaryExpression(+, -), throw
ConstantExpressionβ€”
ParameterExpression
MethodCallExpression, MethodInfo
IndexExpression
BlockExpression, .
ConditionalExpressionβ€” if-else
LabelTargetgoto
LabelExpression, . LabelTarget. , GotoExpression, β€” . void, .
GotoExpression. . ( .. Β«breakΒ»)
LoopExpression, Β«breakΒ»
SwitchCaseSwitchExpression
SwitchExpressionswitch/case
TryExpressiontry/catch/finally/fault
CatchBlock, ,
ElementInitIEnumerable. ListInitExpression
ListInitExpression+
DefaultExpression
NewArrayExpression+
NewExpression
/
MemberAssignment
MemberBinding, , ,
MemberExpression/
MemberInitExpression
MemberListBinding/
MemberMemberBinding/ , /
LambdaExpression
InvocationExpression-
DebugInfoExpression.
SymbolDocumentInfo, .
DynamicExpression( )
RuntimeVariablesExpression/
TypeBinaryExpression, (is)



  • ExpressionVisitor β€” . .
  • DynamicExpressionVisitor β€” DynamicExpression ( VisitDynamic)


Informasi ini cukup untuk mulai membandingkan metode bekerja dengan pohon ekspresi. Saya memutuskan untuk menguraikan semua ini dengan contoh menemukan turunannya. Saya tidak melihat semua opsi yang mungkin - hanya yang dasar. Tetapi jika karena alasan tertentu seseorang memutuskan untuk memodifikasi dan menggunakannya, saya akan dengan senang hati membagikan peningkatan melalui permintaan ke repositori saya .


Pencocokan pola


Jadi, tugasnya adalah membuat kalkulus derivatif. Anda dapat memperkirakan yang berikut ini: ada beberapa aturan untuk menemukan turunan untuk berbagai jenis operasi - penggandaan, pembagian, dll. Bergantung pada operasinya, Anda harus memilih formula tertentu. Dalam formulasi dangkal seperti itu, tugas idealnya ditempatkan pada sakelar / kasing . Dan dalam versi bahasa terbaru, kami disajikan dengan switch / case 2.0 atau pencocokan pola .

Sulit untuk membahas sesuatu di sini. Pada hub, jumlah kode seperti itu terlihat rumit dan kurang dibaca, jadi saya sarankan melihat github . Sebagai contoh turunan, ternyata seperti ini:

Contoh
    public class PatterntMatchingDerivative
    {
        private readonly MethodInfo _pow = typeof(Math).GetMethod(nameof(Math.Pow));
        private readonly MethodInfo _log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });
        private readonly ConstantExpression _zero = Expression.Constant(0d, typeof(double));
        private readonly ConstantExpression _one = Expression.Constant(1d, typeof(double));
		
        public Expression<Func<double, double>> ParseDerivative(Expression<Func<double, double>> function)
        {
            return Expression.Lambda<Func<double, double>>(ParseDerivative(function.Body), function.Parameters);
        }

        private Expression ParseDerivative(Expression function) => function switch
        {
            BinaryExpression binaryExpr => function.NodeType switch
            {
                ExpressionType.Add => Expression.Add(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),
                ExpressionType.Subtract => Expression.Subtract(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),

                ExpressionType.Multiply => (binaryExpr.Left, binaryExpr.Right) switch
		{	
	  	    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression _) => constant,
		    (ParameterExpression _, ConstantExpression constant) => constant,
		    _ => Expression.Add(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right)))
		},

                ExpressionType.Divide => (binaryExpr.Left, binaryExpr.Right) switch
		{
		    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression parameter) => Expression.Divide(constant, Expression.Multiply(parameter, parameter)),
		    (ParameterExpression _, ConstantExpression constant) => Expression.Divide(_one, constant),
		    _ => Expression.Divide(Expression.Subtract(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right))
	        },
            },
            MethodCallExpression methodCall when methodCall.Method == _pow => (methodCall.Arguments[0], methodCall.Arguments[1]) switch
            {
                (ConstantExpression constant, ParameterExpression _) => Expression.Multiply(methodCall, Expression.Call(null, _log, constant)),
                (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, _pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))),
                (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(ParseDerivative(expression), methodCall), Expression.Call(null, _log, constant)),
             },
             _ => function.NodeType switch
            {
                ExpressionType.Constant => _zero,
                ExpressionType.Parameter => _one,
                _ => throw new OutOfMemoryException("Bitmap best practice")
             }
        };
    }


Terlihat agak tidak biasa, tetapi menarik. Senang sekali menulis ini - semua persyaratan cocok secara organik pada satu baris.

Contoh berbicara sendiri, Anda tidak dapat menggambarkannya lebih baik dengan kata-kata.

Pengunjung naif


Dalam tugas seperti itu, seorang pengunjung pohon ekspresi segera muncul di benak, yang membuat banyak suara
dan sedikit panik di antara para amatir untuk membahas lincah di dapur. "Takut bukan karena ketidaktahuan, tetapi pengetahuan palsu. Lebih baik tidak mengetahui apa pun daripada mempertimbangkan kebenaran yang tidak benar. " Mengingat frasa Tolstoy yang indah ini, mengakui ketidaktahuan dan meminta dukungan Google, Anda dapat menemukan panduan berikut .

Saya memiliki tautan ini adalah yang pertama (setelah Siberia pada tahun 1949) untuk kueri "Pengunjung pohon ekspresi".
Sekilas, inilah tepatnya yang kita butuhkan. Judul artikel sesuai dengan apa yang ingin kita lakukan, dan kelas-kelas dalam contoh diberi nama dengan suffix Visitor .

Setelah meninjau artikel dan membuat, dengan analogi untuk contoh kita dengan turunannya, kita mendapatkan:
Tautan ke github.

Contoh
public class CustomDerivativeExpressionTreeVisitor
    {
        public Expression<Func<double, double>> Visit(Expression<Func<double, double>> function)
        {
            return Expression.Lambda<Func<double, double>>(Visitor.CreateFromExpression(function.Body).Visit(), function.Parameters);
        }
    }

    public abstract class Visitor
    {
        protected static readonly MethodInfo Pow = typeof(Math).GetMethod(nameof(Math.Pow));
        protected static readonly MethodInfo Log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });
        protected readonly ConstantExpression Zero = Expression.Constant(0d, typeof(double));
        protected readonly ConstantExpression One = Expression.Constant(1d, typeof(double));
        public abstract Expression Visit();
        public static Visitor CreateFromExpression(Expression node)
            => node switch
            {
                BinaryExpression be => new BinaryVisitor(be),
                MethodCallExpression mce when mce.Method == Pow => new PowMethodCallVisitor(mce),
                _ => new SimpleVisitor(node),
            };
        
    }

    public class BinaryVisitor : Visitor
    {
        private readonly BinaryExpression _node;
        
        public BinaryVisitor(BinaryExpression node)
        {
            _node = node;
        }

        public override Expression Visit()
            => _node.NodeType switch
            {
                ExpressionType.Add => Expression.Add(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),
                ExpressionType.Subtract => Expression.Subtract(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),

                ExpressionType.Multiply => (binaryExpr.Left, binaryExpr.Right) switch
		{	
	  	    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression _) => constant,
		    (ParameterExpression _, ConstantExpression constant) => constant,
		    _ => Expression.Add(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right)))
		},

                ExpressionType.Divide => (binaryExpr.Left, binaryExpr.Right) switch
		{
		    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression parameter) => Expression.Divide(constant, Expression.Multiply(parameter, parameter)),
		    (ParameterExpression _, ConstantExpression constant) => Expression.Divide(_one, constant),
		    _ => Expression.Divide(Expression.Subtract(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right))
	        },
            };
    }

    public class PowMethodCallVisitor : Visitor
    {
        private readonly MethodCallExpression _node;

        public PowMethodCallVisitor(MethodCallExpression node)
        {
            _node = node;
        }

        public override Expression Visit()
            => (_node.Arguments[0], _node.Arguments[1]) switch
            {
                (ConstantExpression constant, ParameterExpression _) => Expression.Multiply(_node, Expression.Call(null, Log, constant)),
                (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, Pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))),
                (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(CreateFromExpression(expression).Visit(), _node), Expression.Call(null, Log, constant)),
            };
    }

    public class SimpleVisitor : Visitor
    {
        private readonly Expression _node;

        public SimpleVisitor(Expression node)
        {
            _node = node;
        }

        public override Expression Visit()
            => _node.NodeType switch
            {
                ExpressionType.Constant => Zero,
                ExpressionType.Parameter => One,
            };
    }


Faktanya - kami menyebarkan switch case untuk kelas yang berbeda. Tidak kurang dari mereka, sihir tidak muncul. Semua kasus yang sama, lebih banyak garis. Dan di mana pengiriman kiriman ganda dijanjikan?

Pengunjung klasik dan pengiriman ganda


Di sini perlu diceritakan tentang template Pengunjung itu sendiri , itu juga Pengunjung , yang merupakan dasar dari pengunjung pohon Ekspresi . Mari kita menganalisisnya hanya pada contoh pohon ekspresi.

Untuk sesaat, misalkan kita merancang pohon ekspresi. Kami ingin memberi pengguna kemampuan untuk beralih melalui pohon ekspresi dan, tergantung pada jenis simpul (jenis Ekspresi), lakukan tindakan tertentu.

Opsi pertama adalah tidak melakukan apa-apa. Artinya, memaksa pengguna untuk menggunakan sakelar / kasing. Ini bukan pilihan yang buruk. Tapi di sini ada nuansa seperti itu: kami menyebarkan logika yang bertanggung jawab untuk jenis tertentu. Sederhananya, polimorfisme dan tantangan virtual ( alias terlambat mengikat) memungkinkan untuk menggeser definisi tipe ke runtime dan menghapus pemeriksaan ini dari kode kami. Cukup bagi kita untuk memiliki logika yang menciptakan instance dari tipe yang diinginkan, maka semuanya akan dilakukan oleh runtime untuk kita.

Opsi kedua.Solusi yang jelas adalah menarik logika ke dalam metode virtual. Dengan mengganti metode virtual di setiap penggantinya, kita bisa melupakan sakelar / kasing. Mekanisme panggilan polimorfik akan memutuskan untuk kita. Tabel metode akan bekerja di sini, metode akan dipanggil oleh offset di dalamnya. Tapi ini adalah topik untuk seluruh artikel, jadi jangan terbawa suasana. Metode virtual sepertinya bisa menyelesaikan masalah kita. Namun sayangnya, mereka membuat yang lain. Untuk tugas kami, kami dapat menambahkan metode GetDeriviative (). Tapi sekarang kelas ekspresi sendiri terlihat aneh. Kita dapat menambahkan metode semacam itu untuk semua kesempatan, tetapi mereka tidak cocok dengan logika umum kelas. Dan kami masih tidak memberikan kesempatan untuk melakukan sesuatu yang mirip dengan pengguna (tentu saja dengan cara yang memadai). Kita perlu membiarkan pengguna menentukan logika untuk setiap jenis tertentu,tetapi pertahankan polimorfisme (yang tersedia untuk kita).

Hanya upaya pengguna untuk melakukan ini tidak akan berhasil.

Di sinilah letak pengunjung sebenarnya. Dalam tipe hierarki dasar (Ekspresi dalam kasus kami), kami mendefinisikan metode formulir

virtual Expression Accept(ExpressionVisitor visitor);

Dalam ahli waris, metode ini akan diganti.

ExpressionVisitor sendiri adalah kelas dasar yang berisi metode virtual dengan tanda tangan yang sama
untuk setiap jenis hierarki. Menggunakan kelas ExpressionVisitor sebagai contoh, VisitBinary (...), VisitMethodCall (...), VisitConstant (...), VisitParameter (...).

Metode-metode ini disebut dalam kelas yang sesuai dari hierarki kita.

Itu Metode Terima di kelas BinaryExpression akan terlihat seperti ini:

	
protected internal override Expression Accept(ExpressionVisitor visitor)
{
        return visitor.VisitBinary(this);
}

Akibatnya, untuk mendefinisikan perilaku baru, pengguna hanya perlu membuat pewaris kelas ExpressionVisitor, di mana metode yang sesuai untuk menyelesaikan satu masalah akan didefinisikan ulang. Dalam kasus kami, DerivativeExpressionVisitor dibuat.

Lebih lanjut, kami memiliki beberapa objek penerus Ekspresi, tetapi mana yang tidak diketahui, tetapi tidak perlu.
Kami memanggil metode Terima virtual dengan implementasi ExpressionVisitor yang kami butuhkan, yaitu dengan DerivativeExpressionVisitor. Berkat pengiriman dinamis, implementasi Terima yang ditimpa disebut, seperti run-time, katakan BinaryExpression. Dalam tubuh metode ini, kami sangat memahami bahwa kami berada di BinaryExpression, tetapi kami tidak tahu penerus ExpressionVisitor mana yang datang kepada kami. Tapi sejak itu VisitBinary juga virtual, kita tidak perlu tahu. Sekali lagi, kita cukup memanggil dengan merujuk ke kelas dasar, panggilan tersebut secara dinamis (pada saat run time) dikirim dan implementasi VisitBinary yang dikesampingkan dari jenis runtime dipanggil. Begitu banyak untuk pengiriman ganda - ping-pong dengan gaya "Anda melakukannya" - "tidak, Anda".

Apa manfaatnya bagi kita. Bahkan, ini memungkinkan untuk "menambahkan" metode virtual dari luar, bukan
mengubah kelas. Kedengarannya hebat, tetapi memiliki kelemahan:

  1. Beberapa kiri dalam bentuk metode Terima, yang bertanggung jawab untuk segalanya dan untuk apa-apa pada saat yang sama
  2. Efek riak dari fungsi hash yang baik adalah bahwa ketika Anda menambahkan hanya satu pewaris hierarki, dalam kasus terburuk, semua orang harus menyelesaikan pengunjung mereka

Tetapi sifat pohon ekspresi memungkinkan biaya ini karena spesifik bekerja dengan ekspresi, karena solusi semacam ini adalah salah satu fitur utama mereka.
Di sini Anda dapat melihat semua metode yang tersedia untuk kelebihan beban.

Jadi, mari kita lihat bagaimana tampilannya pada akhirnya.
Tautan ke github.

Contoh
public class BuildinExpressionTreeVisitor : ExpressionVisitor
    {
        private readonly MethodInfo _pow = typeof(Math).GetMethod(nameof(Math.Pow));
        private readonly MethodInfo _log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });
        private readonly ConstantExpression _zero = Expression.Constant(0d, typeof(double));
        private readonly ConstantExpression _one = Expression.Constant(1d, typeof(double));

        public Expression<Func<double, double>> GetDerivative(Expression<Func<double, double>> function)
        {
            return Expression.Lambda<Func<double, double>>(Visit(function.Body), function.Parameters);
        }

        protected override Expression VisitBinary(BinaryExpression binaryExpr)
            => binaryExpr.NodeType switch
            {
                ExpressionType.Add => Expression.Add(Visit(binaryExpr.Left), Visit(binaryExpr.Right)),
                ExpressionType.Subtract => Expression.Subtract(Visit(binaryExpr.Left), Visit(binaryExpr.Right)),

                ExpressionType.Multiply when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ConstantExpression => _zero,
                ExpressionType.Multiply when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ParameterExpression => binaryExpr.Left,
                ExpressionType.Multiply when binaryExpr.Left is ParameterExpression && binaryExpr.Right is ConstantExpression => binaryExpr.Right,
                ExpressionType.Multiply => Expression.Add(Expression.Multiply(Visit(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, Visit(binaryExpr.Right))),

                ExpressionType.Divide when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ConstantExpression => _zero,
                ExpressionType.Divide when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ParameterExpression => Expression.Divide(binaryExpr.Left, Expression.Multiply(binaryExpr.Right, binaryExpr.Right)),
                ExpressionType.Divide when binaryExpr.Left is ParameterExpression && binaryExpr.Right is ConstantExpression => Expression.Divide(_one, binaryExpr.Right),
                ExpressionType.Divide => Expression.Divide(Expression.Subtract(Expression.Multiply(Visit(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, Visit(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right)),
            };

        protected override Expression VisitMethodCall(MethodCallExpression methodCall)
            => (methodCall.Arguments[0], methodCall.Arguments[1]) switch
            {
                (ConstantExpression constant, ParameterExpression _) => Expression.Multiply(methodCall, Expression.Call(null, _log, constant)),
                (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, _pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))),
                (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(Visit(expression), methodCall), Expression.Call(null, _log, constant)),
            };

        protected override Expression VisitConstant(ConstantExpression _) => _zero;

        protected override Expression VisitParameter(ParameterExpression b) => _one;
    }


temuan


Mungkin, seperti dalam kebanyakan tugas pemrograman, jawaban yang pasti tidak dapat diberikan. Semuanya, seperti biasa, tergantung pada situasi spesifik. Saya suka pencocokan pola yang biasa untuk contoh saya, karena Saya tidak mengembangkannya ke skala pengembangan industri. Jika ungkapan ini akan meningkat tanpa terkendali, ada baiknya memikirkan pengunjung. Dan bahkan pengunjung yang naif memiliki hak untuk hidup - setelah semua, ini adalah cara yang baik untuk menyebarkan sejumlah besar kode ke dalam kelas jika hierarki belum memberikan dukungan pada bagiannya. Dan bahkan di sini ada pengecualian.

Demikian juga, dukungan pengunjung dari hierarki adalah hal yang sangat kontroversial.

Tetapi saya berharap bahwa informasi yang diberikan di sini cukup untuk membuat pilihan yang tepat.

Source: https://habr.com/ru/post/undefined/


All Articles