MIT · sıfır dış bağımlılık · saf BCL

DSO.Core.Evoker

Reflection'ın esnekliği, derlenmiş kodun hızı, JSON'un taşınabilirliği — tek bir bağımlılıksız çekirdekte.

.NET'te isimle metot çağırmak, çalışma zamanında gerçek CLR tipleri üretmek, bu tiplerin üyelerine boxing'siz erişmek ve herhangi bir sınıfı JSON komutlarla kullanılabilir hale getirmek için yazılmış çekirdek kütüphane. Yalnızca System.Reflection.Emit, System.Linq.Expressions ve System.Text.Json kullanır.

.NET 6.0+ bağımlılık yok test 228 kontrol lisans MIT
01 · Çağır

İsimle metot

Overload seçimi, optional parametreler, async — tipli yolda 0 B allocation.

02 · Üret

Gerçek CLR tipleri

Reflection.Emit ile milisaniyelerde, şema cache'li tip üretimi.

03 · Eriş

Boxing'siz erişim

Derlenmiş getter/setter'lar; materyalizasyonda ~194 kat hız.

04 · Aç

JSON komutlar

Herhangi bir sınıfı kod değiştirmeden {"op":"invoke"} ile kullan.

Bir kural motoru, ORM materyalizasyonu, plugin sistemi, dinamik form/rapor üreticisi, uzaktan yönetim API'si ya da "bu DLL'deki şu metodu ekrandan çağırayım" ihtiyacı: hepsi aynı çekirdeğin üzerinde, aynı kurallarla çalışır.

Aile

Evoker çekirdeği tek başına kullanılabilir; ihtiyaç büyüdükçe aile paketleri aynı kurallar üzerine katman ekler.

Neden Evoker?

Çalışma zamanında "isimle bir şey yapmak" için yaygın yaklaşımların her biri bir bedel ödetir:

Yaygın yaklaşımBedeliEvoker'ın cevabı
Klasik reflection (MethodInfo.Invoke)Her çağrıda argüman dizisi, boxing, güvenlik kontrolleri; ~25 kat yavaşBir kez derlenen expression-tree delegate'leri; tipli sürümde 0 byte allocation
dynamic / DLRGerçek tip yok; güçlü tipli API'lere (ORM, serializer, DI) verilemezGerçek CLR tipleri üretir; interface implement eder, her API'ye verilebilir
Property-bag dinamik nesnelerInterface yok, hücre başına allocation, tip güvenliği yokIL ile üretilmiş gerçek property'ler, şema cache'i, tipli getter/setter
Interceptor'lı proxy çatılarıHer çağrı tek bir interceptor'dan geçer; ağır, ince kontrol zorMetot başına bağımsız forwarder, metot başına ayrı delegate
"Önce derle, sonra çağır" üreteçleriDerleyici bağımlılığı, saniyeler süren üretim, büyük bellekRoslyn yok; Reflection.Emit ile milisaniyeler

Sıradan kütüphanelerin genelde düşünmediği şeyler

  • Instance-güvenli cache. Derlenmiş delegate'ler instance'ı içinde taşımaz. Aynı tip + metot için farklı nesnelerle çalışan iki builder birbirinin sonucunu asla ezemez.
  • Akıllı overload seçimi. Önce birebir ad, sonra büyük/küçük harf duyarsız ad (VB.NET dostu); ardından argüman sayısı, optional parametreler ve tip uyumu. JSON komutlarda ayrıca sayısal tercih (int → long → …) ve params desteği.
  • Unload dostu. Her cache'in bir "unut" kapısı var (ForgetType, ForgetAssembly); collectible AssemblyLoadContext'ler gerçekten bellekten atılabilir.
  • JSON-native. Herhangi bir sınıf, değişiklik yapmadan JSON komutlarla kullanılabilir. Tanım (describe) ve hazır komut şablonları otomatik üretilir.

Rakamlar

Ölçümler TestKit içindeki PerfBench ile .NET 8, Release derlemesinde alındı. Mutlak değerler makineye göre değişir; oranlar belirleyicidir.

~194×
200.000 satır materyalizasyonda reflection'a göre hız
0 B
Tipli delegate ve accessor yollarında çağrı başına allocation
~%99
Materyalizasyonda daha az bellek tahsisi

Çağırma — süre / çağrı

Daha kısa çubuk daha iyi.
MethodInfo.Invoke (klasik)
92.4 ns
Invoke<int>("Add", a, b)
~50 ns
GetFunc<int>(...) önceden çözülmüş
~40 ns
GetFunc<int,int,int>("Add")
~8–10 ns
Doğrudan derlenmiş delegate (teorik alt sınır)
3.7 ns
Çağırma yoluSüre / çağrıAllocation / çağrı
Doğrudan derlenmiş, tipli delegate (teorik alt sınır)3.7 ns0 B
MethodInfo.Invoke (klasik reflection)92.4 ns112 B
EvokerBuilder.Invoke<int>("Add", a, b)~50 ns88 B*
EvokerBuilder.GetFunc<int>(...) (önceden çözülmüş)~40 ns88 B*
EvokerBuilder.GetFunc<int,int,int>("Add")~8–10 ns0 B

* Çağıranın oluşturduğu object[] dizisi.

Property / field erişimiSüreAllocation
FieldInfo.GetValue (klasik reflection)76.3 ns24 B
DynamicEntityAccessor getter delegate'i4.1 ns0 B
EvokerBuilder.GetValue<int>("Counter")43 ns0 B
Materyalizasyon (200.000 satır)SüreToplam alloc.Satır başı
Reflection (Activator + isimle set)777 ms659 MB~3456 B
DynamicEntityAccessor4 ms9 MB~48 B
Tip çözmeSüre
EvokerEngine.ResolveType cache isabeti23 ns, 0 B
Bulunamayan ad (ilk sorgu: tüm assembly'ler taranır)~15 ms
Aynı bulunamayan ad tekrar (negatif cache)~45 µs

Kurulum

Depoyu klonlayıp projenize referans olarak ekleyin:

Hedef.NET 6.0 ve üzeri
BağımlılıkYok — yalnızca BCL
Ad alanlarıDSO.Core.Evoker, DSO.Core.Evoker.Commands, DSO.Core.Evoker.Conversion, DSO.Core.Evoker.Description

60 saniyede Evoker

Dört temel yeteneğin her biri tek bir örnekte:

API Rehberi

Bu bölüm her sınıfın public yüzeyini örnekleriyle listeler; kütüphaneyi kullanırken başvuru kaynağı olarak kullanabilirsiniz.

EvokerEngine

statik giriş noktasıDSO.Core.Evoker

Tek satırlık çağrılar ve isimden tip çözme için.

ResolveType arama sırası

Type.GetType — assembly-qualified ad
Tam ad birebir, yüklü tüm assembly'lerde
Tam ad büyük/küçük harf duyarsız
Kısa ad birebir, sonra duyarsız

Bir seviyede birden fazla farklı tip eşleşirse (ör. iki plugin'de de Result sınıfı varsa) Evoker tahmin etmez: AmbiguousMatchException ile adayları listeler. Kısmen yüklenebilen assembly'ler (ReflectionTypeLoadException) aramayı düşürmez.

✓

Collectible context'lerdeki tipler zayıf referansla cache'lenir: plugin unload'u engellenmez ve reload sonrası eski tip dönmez. Bulunamayan adlar da cache'lenir; yeni bir assembly yüklendiğinde bu cache kendiliğinden boşalır.

EvokerBuilder

isimle çağırmaDSO.Core.Evoker

Esnek, genel amaçlı çağırma motoru. object[] argümanlarla çalışır; sıkı döngüler için tipli delegate'ler sunar.

Oluşturma ve instance modu

Çağırma

Metot seçme kuralları (FindMethod)

  1. İsim önce birebir aranır, bulunamazsa büyük/küçük harf duyarsız aranır (VB.NET'te add ile Add aynıdır).
  2. Parametre sayısı argüman sayısına eşit olan adaylar önceliklidir. Yoksa fazlalık parametrelerinin hepsi optional olan adaylar kullanılır; eksikler metodun kendi varsayılan değerleriyle dolar (C# = değer, VB.NET Optional).
  3. Birden fazla aday kalırsa argüman tiplerine bakılır: önce birebir tip, sonra atanabilir tip.
  4. Uygun aday yoksa MissingMethodException fırlatılır; mesajda mevcut imzalar listelenir.

Aynı kuralları dışarıdan da kullanabilirsiniz:

Constructor seçimi

Constructor'lar metotlarla aynı kurallarla seçilir: sayı, optional, tip uyumu, sayısal genişletme (ör. int parametreye long). Değerler gerekirse EvokerValueConverter ile dönüştürülür. Tüm parametreleri optional olan bir constructor "parametresiz" sayılır.

Önceden çözülmüş delegate'ler (object[] tabanlı)

Tipli delegate'ler — boxing yok, allocation yok

Sıkı döngüler için en hızlı yol: argüman ve dönüş tipleri derleme zamanında bilinir. Son tip parametresi, .NET'in Func<> kuralındaki gibi dönüş tipidir.

  • Overload, T1..Tn tiplerine göre seçilir.
  • Tip metodun parametresinden farklıysa (int → long, türetilmiş → taban) dönüşüm derlenir; mümkün değilse InvalidCastException fırlatılır.
  • Verilmeyen optional parametreler varsayılanla dolar.
  • SetInstance verilmişse nesne delegate'e bağlanır; verilmemişse her çağrıda çözülür.
  • Sıfır argümanlı tipli sürüm bilerek yoktur; argümansız metotlarda GetFunc<T>(ad) zaten boxing yapmaz.

Property ve field erişimi EvokerBuilderPropertyExtensions

!

GetValue/SetValue yalnızca SetInstance ile sabit bir nesneye bağlı builder'larda anlamlıdır; değilse InvalidOperationException fırlatılır.

Event'ler EvokerBuilderEventExtensions

Derleme zamanında delegate tipini bilmeden herhangi bir event'e abone olun. EventHandler, EventHandler<T>, özel delegate'ler ve VB.NET'in ürettiği gizli delegate'lerin hepsi desteklenir.

Static event'ler de desteklenir. Instance event'leri için builder'ın SetInstance ile bağlı olması gerekir.

Cache yönetimi

!

ref / out: object[] sözleşmesi bu değerleri çağırana geri taşıyamadığı için ref/out parametreli metotlar açık bir NotSupportedException ile reddedilir; değerler sessizce kaybolmaz. Bu tür metotlar için DSO.Core.Evoker.Extend ya da düz reflection kullanın.

DynamicEntityAccessor

sıfır allocation erişim

ORM satır okuma döngüsü gibi sıcak yollar için. Bilinen bir property, bilinen bir tiple, boxing olmadan okunur ve yazılır.

  • Property yoksa aynı isimli field'a düşer.
  • includeNonPublic: true ile private/protected/internal üyeler de görülür.
  • Cache anahtarı struct'tır; cache isabetinde bile string üretilmez.

DynamicTypeFactory

çalışma zamanında tip üretimi

Gerçek CLR tipleri üretir. Tüm tipler tek, paylaşımlı bir modülde üretilir ve aynı şema ikinci kez istendiğinde IL yeniden üretilmez.

Genişletme parametreleri

Çekirdek bunlara ihtiyaç duymaz; Extend paketi kullanır.

configureType verildiğinde şema cache'i bilinçli olarak devre dışı kalır: aynı şema farklı yapılandırmalarla farklı tipler üretebilir. TypeMembers yapısı emisyonu biten property, metot, generic metot ve event builder'larını verir. InvokeGenericMethodDelegate generic forwarder'ların çağırdığı public yardımcıdır.

DynamicDelegateTypeFactory

her imzaya delegate tipi

Func<>/Action<>'ın ifade edemediği imzalar için (ref/out parametreler, 16'dan fazla parametre) gerçek bir delegate tipi üretir. Aynı imza tekrar istenirse aynı tip döner.

DynamicClass

hepsi tek bir akışta

DynamicTypeFactory + DynamicEntityAccessor + EvokerBuilder üçlüsünü tek bir akıcı (fluent) nesnede toplar. Thread-safe build sunar: aynı tanım birden fazla thread'den kurulsa bile tip ve instance yalnızca bir kez üretilir.

Oluşturma

Property'ler ve değerler

Değişiklik kancaları (instance'a özel)

Metotlar

Metot gövdesi bir delegate'tir; istediğiniz an değiştirebilirsiniz.

Generic metotlar (type-erasure)

Event'ler

Gerçek C# += söz dizimiyle abone olmak için tipi bir interface'e bağlayın: Extend → Implement<T>.

Attribute ekleme

Yaşam döngüsü ve genişletme

i

Cache ömrü: Üretilen tip ve derlenmiş accessor'lar paylaşımlı, process ömürlü cache'lere aittir. Her seferinde farklı kolon seti üreten senaryolarda useSchemaCache: false, forgetOnDispose: true kullanıp using ile temizleyin.

Commands

JSON komutlarDSO.Core.Evoker.Commands

Herhangi bir sınıfı kodunda hiçbir değişiklik yapmadan JSON ile kullanılabilir hale getirir. Aynı komut biçimi her yerde çalışır: bir tipte (EvokerTarget), bir plugin'de (PluginTarget, sandbox worker'ın içinde bile) ve web üzerinden (DSO.Core.Evoker.Api).

Komut biçimi

Alan adları büyük/küçük harf duyarsızdır. op verilmezse invoke kabul edilir.

Argüman bağlama kuralları

  • Sıralı: argüman sayısı ≤ parametre sayısı olmalı; kalan parametreler optional olmalı.
  • İsimli: her ad bir parametreye karşılık gelmeli; verilmeyen parametreler optional olmalı.
  • Tek parametreli metot: isimli nesne hiçbir adayla eşleşmezse, nesnenin kendisi o tek parametrenin değeri sayılır (SaveCustomer(Customer c)'ye doğrudan { "Code": "C1" }).
  • Puanlama: adaylar JSON değer türü uyumuna göre puanlanır. Tam sayı önce int, sonra long ve diğerleri; ondalık önce double, sonra decimal, sonra float. Değer tipe sığmalıdır (300 byte'a gitmez). Metin; string, tarih, Guid veya enum adı olabilir. Nesne, sınıf ya da dictionary olabilir.
  • params / ParamArray: argümanlar tek tek de verilebilir (["A", "B"]); hiç verilmezse boş dizi geçer.
  • Belirsizlik: eşit puanlı adaylar kalırsa AmbiguousMatch döner ve argTypes ile belirtmeniz istenir.

Komutları koddan üretmek

EvokerTarget ve nesne ömrü

EvokerLifetime, .NET'in DI ömürleriyle aynı mantıkta çalışır:

ÖmürDavranış
Singleton (varsayılan)Tek nesne; durum komutlar arasında korunur
ScopedKomut başına bir nesne; çok adımlı komutun adımları aynı nesneyi paylaşır
TransientHer adımda yeni nesne
StaticNesne yok; static sınıflar otomatik olarak bu ömrü alır

Özellikler: Type, Lifetime, IncludeNonPublic, Kind ("Type" / "Instance"), TypeFullName, Instance. Kendi hedef türünüzü yazmak için IEvokerTarget arayüzünü (Kind, TypeFullName, ExecuteAsync, DescribeAsync) uygulayın; Plugins paketindeki PluginTarget bu arayüzü kullanır.

EvokerCatalog — Guid anahtarlı hedef listesi

!

İsimle erişim ve izin listesi: Web'e açılan senaryolar için kayıt gerektirmeyen tipler sadece izin verilen kalıplarla açılır. Varsayılan kapalıdır.

Sonuç ve hata modeli

Komutlar exception fırlatmaz; hatalar sonucun içinde döner. Bu yüzden web'e doğrudan verilebilir.

TipÜyeler
EvokerCommandResultSuccess, Result, Steps, Error, ElapsedMs, Mode, ToJson(indented), FromJson(json), Fail(...), Options
EvokerStepResultIndex, Op, Member, As, Success, Result, Error, ElapsedMs, Skipped
EvokerErrorCode, Message, ExceptionType, StepIndex, BatchIndex, Member
EvokerCommandExceptionCode, TargetExceptionType, BatchIndex (kod içinden fırlatmak için)
EvokerErrorCodesHTTPAnlamı
BadRequest400Komut okunamadı / geçersiz
NotAllowed403İzin listesi dışında
TargetNotFound404Hedef yok
MemberNotFound404Üye yok
Inactive409Hedef pasif (plugin)
InvalidArguments422Argümanlar hiçbir imzaya uymuyor
AmbiguousMatch422Birden fazla imza eşit uyuyor
InvalidOperation422İşlem bu hedefte yapılamaz
Cancelled499İstek iptal edildi
TargetException500Hedefin kendi kodu exception fırlattı
InternalError500Beklenmeyen hata
Timeout504timeoutMs aşıldı

HTTP karşılığı EvokerErrorCodes.HttpStatus(code) ile alınır.

Conversion

değer dönüştürmeDSO.Core.Evoker.Conversion

Tüm katmanların ortak dönüştürücüsü.

ConvertTo, aynı şekle sahip iki farklı tip arasında (ör. plugin'in Point'i ile host'un PointDto'su) dönüşümü dahili, derlenmiş bir kopyalayıcıyla yapar. Bu kopyalayıcı sonucu JSON yoluyla birebir aynı olan şekillerde kullanılır; diğer durumlarda JSON'a düşülür.

i

Unload kapıları: EvokerValueConverter.ForgetAssembly(asm) ve EvokerJson.ForgetAssembly(asm) çağrıları, System.Text.Json'ın global cache'leri dahil, bir assembly'ye ait tüm metadata'yı bırakır.

Description

tip tanımı ve şablonlarDSO.Core.Evoker.Description

Bir tipin tam tanımını ve her üye için hazır JSON komut şablonlarını üretir. Hem çalışan tiplerle hem de DLL'i çalıştırmadan (MetadataLoadContext tipleriyle) çalışır.

Tanım modeli

EvokerTypeDescriptor

FullName, Name, Namespace, AssemblyName, Kind, BaseType, Interfaces, Constructors, Methods, Properties, Fields, Events, Values, Warnings

Metot tanımı

Ad, görünürlük, dönüş tipi; parametreler (yön, optional, varsayılan, params); IsStatic/IsAsync/IsVirtual/IsAbstract/IsOverride; DeclaredIn; okunur Signature; NotCallableReason; hazır Sample.

Property tanımı

Get ve set için ayrı görünürlük (ör. Getter=Public, Setter=Private); init, static ve indexer bilgisi; o anki Value; Sample ve SampleSet şablonları.

EvokerTypeKind

Class, StaticClass, AbstractClass, Record, Struct, Enum, Interface, Delegate

Örnek imza çıktısı: "Task<int> AddAsync(int a, int b = 5)"

Plugin ve unload dostu tasarım

Collectible AssemblyLoadContext'ler, içlerindeki bir tipe güçlü referans tutan tek bir statik cache yüzünden bile bellekten atılamaz. Evoker'daki her cache'in bu yüzden bir çıkış kapısı vardır:

CacheTemizleme
Derlenmiş metot delegate'leriEvokerBuilder.ForgetType(type)
Accessor ve constructor'larDynamicEntityAccessor.ForgetType(type) / builder.ForgetCache()
Komut üye cache'iEvokerBuilder.ForgetType ile birlikte otomatik
Tip adı cache'iCollectible tipler zaten zayıf referanslı; context unload olunca kendiliğinden silinir
JSON / şekil eşlemeEvokerJson.ForgetAssembly(asm), EvokerValueConverter.ForgetAssembly(asm)

DSO.Core.Evoker.Plugins bunların hepsini unload sırasında kendisi çağırır. UnloadTest paketi, in-process bir plugin'in Evoker ile defalarca kullanıldıktan sonra gerçekten bellekten atıldığını doğrular.

Testler ve örnekler

Projeİçerik
DSO.Core.Evoker.TestRunnerÇekirdek + Extend test koşucusu (228 kontrol). BuilderFeatureTests (F1–F8), CommandTests (C1–C13), ExtensionPointTests (E1–E4) ve Extend testleri.
TestKit/PerfBenchBu sayfadaki tüm performans rakamlarının kaynağı.
DSO.Core.Evoker.Plugins.DemoApiKatalog hedeflerinin (Singleton/Scoped/Transient/Static) web üzerinden kullanımı — demo.http.
DSO.Core.Evoker.TestApiDepodaki örnek ASP.NET Core API projesi.

Beklenen çıktı: her bölüm TÜM ... TESTLERİ GEÇTİ ✅ ile biter.

Bilinen sınırlar

  • Şema ve accessor cache üst sınırları FIFO tahliye kullanır; gerçek LRU değildir.
  • ForgetType/ForgetSchema kendi cache'lerimizi temizler. DynamicTypeFactory'nin ürettiği tipler collectible değildir (AssemblyBuilderAccess.Run); metadata'ları process ömrü boyunca kalır.
  • EvokerBuilder ile ref/out parametreli metot çağrılmaz; bunun yerine açık bir hata verilir.
  • Üretilen tiplerin ToString/Equals/GetHashCode davranışı object'ten gelir.