اشتراکها
به C# 12 و داتنت 8، ویژگی «آزمایشی» جدیدی به نام Interceptors اضافه شدهاست که به آن «monkey patching» هم میگویند. هدف از آن، جایگزین کردن یک پیاده سازی، با پیاده سازی دیگری است. به این ترتیب توسعه دهندگان داتنتی میتوانند فراخوانی متدهایی خاص را رهگیری کرده (interception) و سپس آنرا به فراخوانی یک پیاده سازی جدید، هدایت کنند.
Interceptor چیست؟
از زمان ارائهی NET 8 preview 6 SDK. به بعد، امکان رهگیری هر متدی از کدهای برنامه، به داتنت اضافه شدهاست؛ به همین جهت از واژهی Interceptor/رهگیر در اینجا استفاده میشود. خود تیم داتنت از این قابلیت در جهت بازنویسی پویای قسمتهایی از کدهای زیرساخت داتنت که از Reflection استفاده میکنند، با نگارشهای کامپایل شدهی مختص به برنامهی شما، کمک میگیرند. به این ترتیب سرعت و کارآیی برنامههای داتنت 8، بهبود قابل ملاحظهای را پیدا کردهاند. برای مثال ahead-of-time compilation (AOT) در داتنت 8 و ASP.NET Core 8x بر اساس این ویژگی پیاده سازی شدهاست. این ویژگی جدید، مکمل source generators است که در نگارشهای پیشین داتنت ارائه شده بود.
بررسی Interceptors با تهیهی یک مثال ساده
فرض کنید میخواهیم فراخوانی متد GetText زیر را رهگیری کرده و سپس آنرا با نمونهی دیگری جایگزین کنیم:
برای اینکار ابتدا نیاز است یک فایل جدید را به نام InterceptsLocationAttribute.cs با محتوای زیر به پروژه اضافه کرد:
همانطور که در مقدمهی بحث هم عنوان شد، این ویژگی هنوز آزمایشی است و نهایی نشده و ویژگی فوق نیز هنوز به داتنت اضافه نشدهاست. به همین جهت فعلا باید آنرا به صورت دستی به پروژه اضافه کرد و احتمالا در نگارشهای بعدی داتنت، امضای آن تغییر خواهد کرد ... یا حتی ممکن است بطور کامل حذف شود!
سپس فرض کنید فراخوانی متد GetText در فایل Program.cs برنامه به صورت زیر انجام شدهاست:
یعنی متد GetText، در سطر چهارم و کاراکتر 20 ام آن فراخوانی شدهاست. این اعداد مهم هستند!
در ادامه از این اطلاعات در رهگیر سفارشی زیر استفاده خواهیم کرد:
این رهگیر که به صورت متدی الحاقی برای کلاس InterceptorsSample دربرگیرندهی متد GetText تهیه میشود، کار جایگزینی فراخوانی آنرا در سطر چهارم و کاراکتر 20 ام فایل Program.cs انجام میدهد. امضای پارامترهای این متد، باید با امضای پارامترهای متد رهگیری شده، یکی باشد.
اکنون اگر برنامه را اجرا کنیم ... با خطای زیر مواجه میشویم:
عنوان میکند که این ویژگی آزمایشی است و باید فایل csproj. را به صورت زیر تغییر داد تا بتوان از آن استفاده نمود:
اینبار برنامه کامپایل شده و اجرا میشود. در این حالت خروجی جدید برنامه، خروجی تامین شدهی توسط رهگیر سفارشی ما است:
سؤال: آیا رهگیری انجام شده، در زمان کامپایل انجام میشود یا در زمان اجرا؟
برای این مورد میتوان به Low-Level C# code تولیدی مراجعه کرد. برای مشاهدهی یک چنین کدهایی میتوانید از منوی Tools->IL Viewer برنامهی Rider استفاده کرده و در برگهی ظاهر شده، گزینهی Low-Level C# آنرا انتخاب نمائید:
همانطور که مشاهده میکنید، این رهگیری و جایگزینی، در زمان کامپایل انجام شده و کامپایلر، بهطور کامل نحوهی فراخوانی متد GetText اصلی را به متد رهگیر ما تغییر داده و بازنویسی کردهاست.
سؤال: آیا این قابلیت واقعا کاربردی است؟!
اکنون شاید این سؤال مطرح شود که ... واقعا چه کسی قرار است مسیر کامل یک فایل، شماره سطر و شماره ستون فراخوانی متدی را به اینگونه در اختیار سیستم رهگیری قرار دهد؟! آیا واقعا این قابلیت، یک قابلیت کاربردی و مناسب است؟!
اینجا است که اهمیت source generators مشخص میشود. توسط source generators دسترسی کاملی به syntax trees وجود دارد و همچنین یکسری اطلاعات تکمیلی مانند FilePath و سپس CSharpSyntaxNodeها که دسترسی به دادههای متد ()GetLocation را دارند که مکان دقیق سطر و ستونهای فراخوانیها را مشخص میکند.
کاربردهای فعلی رهگیرها در دات نت 8
در دات نت 8، این موارد با استفاده از رهگیرها بهینه سازی شده و سرعت آنها افزایش یافتهاند:
- فراخوانیهایی که تمام اطلاعات آنها در زمان کامپایل فراهم است، مانند Regex.IsMatch(@"a+b+") که از یک الگوی ثابت و مشخص استفاده میکند، رهگیری شده و پیاده سازی آن با کدی استاتیک، جایگزین میشود.
- در ASP.NET Minimal API، استفاده از lambda expressions جهت ارائهی تعاریفی مانند:
مرسوم است. این نوع فراخوانیها نیز توسط رهگیرها برای جایگزینی handler آنها با کدهای استاتیک، جهت بالابردن کارآیی و کاهش تخصیصهای حافظه انجام میشود.
- بهبود کارآیی foreach loops جهت استفاده از ریاضیات برداری و SIMD در صورت امکان.
- بهبود کارآیی تزریق وابستگیها، زمانیکه به تعاریف مشخصی مانند ()<provider.Register<MyService ختم میشود.
- بجای استفاده از expression trees در زمان اجرای برنامه، اکنون میتوان کدهای SQL معادل را در زمان کامپایل برنامه تولید کرد.
- بهبود کارآیی Serializers، زمانیکه از یک نوع مشخص مانند ()<Serialize<MyType استفاده میشود و کامپایلر میتواند آنرا با کدهای زمان کامپایل، جایگزین کند.
محدودیتهای رهگیرها در داتنت 8
- رهگیرهای داتنت 8 فقط با متدها کار میکنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائهی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمیتوان یک رهگیر جنریک را تعریف کرد.
Interceptor چیست؟
از زمان ارائهی NET 8 preview 6 SDK. به بعد، امکان رهگیری هر متدی از کدهای برنامه، به داتنت اضافه شدهاست؛ به همین جهت از واژهی Interceptor/رهگیر در اینجا استفاده میشود. خود تیم داتنت از این قابلیت در جهت بازنویسی پویای قسمتهایی از کدهای زیرساخت داتنت که از Reflection استفاده میکنند، با نگارشهای کامپایل شدهی مختص به برنامهی شما، کمک میگیرند. به این ترتیب سرعت و کارآیی برنامههای داتنت 8، بهبود قابل ملاحظهای را پیدا کردهاند. برای مثال ahead-of-time compilation (AOT) در داتنت 8 و ASP.NET Core 8x بر اساس این ویژگی پیاده سازی شدهاست. این ویژگی جدید، مکمل source generators است که در نگارشهای پیشین داتنت ارائه شده بود.
بررسی Interceptors با تهیهی یک مثال ساده
فرض کنید میخواهیم فراخوانی متد GetText زیر را رهگیری کرده و سپس آنرا با نمونهی دیگری جایگزین کنیم:
namespace CS8Tests; public class InterceptorsSample { public string GetText(string text) { return $"{text}, World!"; } }
namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public sealed class InterceptsLocationAttribute : Attribute { public InterceptsLocationAttribute(string filePath, int line, int character) { } }
سپس فرض کنید فراخوانی متد GetText در فایل Program.cs برنامه به صورت زیر انجام شدهاست:
using CS8Tests; var example = new InterceptorsSample(); var text = example.GetText("Hello"); Console.WriteLine(text); //Hello, World!
در ادامه از این اطلاعات در رهگیر سفارشی زیر استفاده خواهیم کرد:
using System.Runtime.CompilerServices; namespace CS8Tests; public static class MyInterceptor { [InterceptsLocation("C:\\Path\\To\\CS8Tests\\Program.cs", 4, 20)] public static string InterceptorMethod(this InterceptorsSample example, string text) { return $"{text}, DNT!"; } }
اکنون اگر برنامه را اجرا کنیم ... با خطای زیر مواجه میشویم:
error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add '<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces>' to your project.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <!--<NoWarn>Test001</NoWarn>--> <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces> </PropertyGroup> </Project>
Hello, DNT!
سؤال: آیا رهگیری انجام شده، در زمان کامپایل انجام میشود یا در زمان اجرا؟
برای این مورد میتوان به Low-Level C# code تولیدی مراجعه کرد. برای مشاهدهی یک چنین کدهایی میتوانید از منوی Tools->IL Viewer برنامهی Rider استفاده کرده و در برگهی ظاهر شده، گزینهی Low-Level C# آنرا انتخاب نمائید:
using CS8Tests; using System; using System.Runtime.CompilerServices; [CompilerGenerated] internal class Program { private static void <Main>$(string[] args) { Console.WriteLine(new InterceptorsSample().InterceptorMethod("Hello")); } public Program() { base..ctor(); } }
سؤال: آیا این قابلیت واقعا کاربردی است؟!
اکنون شاید این سؤال مطرح شود که ... واقعا چه کسی قرار است مسیر کامل یک فایل، شماره سطر و شماره ستون فراخوانی متدی را به اینگونه در اختیار سیستم رهگیری قرار دهد؟! آیا واقعا این قابلیت، یک قابلیت کاربردی و مناسب است؟!
اینجا است که اهمیت source generators مشخص میشود. توسط source generators دسترسی کاملی به syntax trees وجود دارد و همچنین یکسری اطلاعات تکمیلی مانند FilePath و سپس CSharpSyntaxNodeها که دسترسی به دادههای متد ()GetLocation را دارند که مکان دقیق سطر و ستونهای فراخوانیها را مشخص میکند.
کاربردهای فعلی رهگیرها در دات نت 8
در دات نت 8، این موارد با استفاده از رهگیرها بهینه سازی شده و سرعت آنها افزایش یافتهاند:
- فراخوانیهایی که تمام اطلاعات آنها در زمان کامپایل فراهم است، مانند Regex.IsMatch(@"a+b+") که از یک الگوی ثابت و مشخص استفاده میکند، رهگیری شده و پیاده سازی آن با کدی استاتیک، جایگزین میشود.
- در ASP.NET Minimal API، استفاده از lambda expressions جهت ارائهی تعاریفی مانند:
app.MapGet("/products", handler: (int? page, int? pageLength, MyDb db) => { ... })
- بهبود کارآیی foreach loops جهت استفاده از ریاضیات برداری و SIMD در صورت امکان.
- بهبود کارآیی تزریق وابستگیها، زمانیکه به تعاریف مشخصی مانند ()<provider.Register<MyService ختم میشود.
- بجای استفاده از expression trees در زمان اجرای برنامه، اکنون میتوان کدهای SQL معادل را در زمان کامپایل برنامه تولید کرد.
- بهبود کارآیی Serializers، زمانیکه از یک نوع مشخص مانند ()<Serialize<MyType استفاده میشود و کامپایلر میتواند آنرا با کدهای زمان کامپایل، جایگزین کند.
محدودیتهای رهگیرها در داتنت 8
- رهگیرهای داتنت 8 فقط با متدها کار میکنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائهی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمیتوان یک رهگیر جنریک را تعریف کرد.
نظرات مطالب
مروری بر Blazor (قسمت اول)
زمانی که از CSharp و به صورت کلی NET. استفاده میکنیم تا برای Android-iOS-Windows و جدیدا Web برنامه بنویسیم، این دغدغه ای که گفتید پیش میآد. علاوه بر راه هایی مثل Obfuscation که همیشه امکان استفاده شون هست، شما راه حل بهتری هم دارید و اون تبدیل کدتون به Native machine code هست که عملا مشکلی که گفتید رو نخواهد داشت و Performance خیلی خیلی خیلی بهتری هم میده. توی Android و iOS این قابلیت در Xamarin دیده شده که اول Native Machine Code با قابلیتی به نام AOT یا Ahead of time به دست بیاد و یک دور هم با LLVM یا Low Level Virtual Machine بهینه سازی هم بشه. اون چیزی که شما به مشتری میدید دیگه DLL نیست و به هیچ روی چیز قابل فهمی هم داخل اش نیست. در برنامههای UWP میتونید از Dot Net Native استفاده کنید. در برنامههای WPF میتونید با پشتیبانی 3 NET Core. از اون، بیایید و خروجی کاملا Windows ای بگیرید که هم احتیاج به نصب بودن Dot Net یا Dot Net Core نداره و هم کدش خوانا نیست. توی Web Assembly هم همین هست، با پشتیبانی Mono از AOT که در مراحل اولیه قرار داره میتونید به سمت مرورگر فایل wasm بفرستید، نه dll، هم مشکلی که گفتید رو نداره و هم تا 70 برابر بسته به سناریو سرعت بیشتری داره.
اشتراکها
مفاهیم و استفاده از Span ها
Puppeteer-Sharp کتابخانه ای برای محیط داتنت است که به توسعهدهندگان این امکان را میدهد تا از Puppeteer، ابزار خودکارسازی مرورگر برای Node.js، در پروژههای خود استفاده کنند. این کتابخانه بهویژه برای رندر کردن صفحات وب، استخراج دادهها، و انجام تستهای خودکار کاربرد دارد. با ویژگیهایی مانند پشتیبانی از JavaScript، امکان گرفتن اسکرینشات و تبدیل به PDF، و قابلیت مدیریت جلسات و کوکیها، Puppeteer-Sharp ابزاری قدرتمند برای خودکارسازی و تست وب به شمار میرود.
dnSpy is a debugger and .NET assembly editor. You can use it to edit and debug assemblies even if you don't have any source code available.
Some Features
- Debug .NET Framework, .NET Core and Unity game assemblies, no source code required
- Set breakpoints and step into any assembly
- Locals, watch, autos windows
- All metadata can be edited
- Edit methods and classes in C# or Visual Basic with IntelliSense, no source code required
- Add new methods, classes or members in C# or Visual Basic
- IL editor for low level IL method body editing
بالاخره تفاوت کارآیی بین حلقههای for و foreach در دات نت 7 برطرف شدهاست که این مورد نیز یکی دیگر از دلایل بهبود کارآیی LINQ در دات نت 7 است. در این مطلب به همراه آزمایشی، این مورد را بررسی خواهیم کرد.
تدارک یک آزمایش برای بررسی کارآیی حلقههای for و foreach در دات نت 7
یک برنامهی کنسول جدید را ایجاد کرده و سپس کتابخانهی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه میکنیم:
در ادامه به این پروژه، کلاس زیر را اضافه میکنیم:
که توسط دستورات زیر در حالت release اجرا شده و نتایج نهایی را نمایش میدهد:
توضیحات:
- میتوان یک پروژه را یکبار بر اساس دات نت 7 و یکبار هم بر اساس دات نت 6 با تغییر target framework آنها کامپایل و اجرا کرد تا بتوان نتایج این دو را با هم مقایسه کرد و یا میتوان با ذکر [SimpleJob(RuntimeMoniker.Net60)] و همچنین [SimpleJob(RuntimeMoniker.Net70)]، این مورد را به صورت خودکار به BenchmarkDotNet دات نت واگذار کرد.
- در این آزمایش، ابتدا یک آرایه و یک لیست را تهیه میکنیم.
- سپس یکبار حلقههای for و foreach را بر روی آرایه و همین عملیات را بر روی لیست تهیه شده، تکرار میکنیم.
نتایج حاصل به صورت زیر هستند:
همانطور که در نتایج فوق هم مشاهده میکنید:
در دات نت 6
- تفاوتی بین کارآیی حلقههای for و foreach، زمانیکه بر روی یک آرایه اجرا میشوند، وجود ندارد.
- اما کارآیی حلقهی foreach نسبت به حلقهی for، زمانیکه بر روی یک لیست اجرا میشوند، تقریبا 50 درصد کمتر است.
در دات نت 7
- تفاوتی بین کارآیی حلقههای for و forach، زمانیکه بر روی یک آرایه اجرا میشوند، وجود ندارد. بنابراین از این لحاظ با دات نت 6 تفاوتی ندارد.
- اما کارآیی حلقهی foreach نسبت به حلقهی for، زمانیکه بر روی یک لیست اجرا میشود، تقریبا یکسان و قابل چشمپوشی است. یعنی در دات نت 7، کارآیی این دو حلقه یکی شدهاست. اما چرا؟
روشی در جهت یافتن یکی بودن سرعت حلقههای for و foreach بر اساس خروجی کامپایلر
با مشاهدهی نتایج حاصل از BenchmarkDotNet میتوان به بهبود کارآیی حاصل پیبرد؛ اما برای مثال چرا زمانیکه از آرایه استفاده میشود، حتی در دات نت 6، تفاوتی بین دو حلقهی for و foreach وجود ندارد، اما زمانیکه از لیستها استفاده میشود، این کارآیی 50 درصد افت میکند؟
در این خروجی بهتر میتوان مشاهده کرد که چرا در حالت استفادهی از آرایهها، تفاوتی بین حلقههای for و foreach نیست؛ چون هر دو به صورت حلقهی for تفسیر میشوند:
اما زمانیکه به لیستها میرسیم، حلقهی foreach به صورت زیر تفسیر میشود که بدیهی است نسبت به حلقهی for، کندتر اجرا خواهد شد:
اگر این خروجی را برای دات نت 6 و دات نت 7 تهیه کنیم، به یک جواب خواهیم رسید. یعنی از دیدگاه #Low-level C، تفاوتی بین IL دات نت 6 و 7 از این لحاظ وجود ندارد. تفاوتی اصلی در بهبودهای JIT دات نت 7 است که سبب شده، خروجی نهایی حلقههای foreach با for یکی باشد.
تدارک یک آزمایش برای بررسی کارآیی حلقههای for و foreach در دات نت 7
یک برنامهی کنسول جدید را ایجاد کرده و سپس کتابخانهی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.13.4" /> </ItemGroup> </Project>
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; namespace NET7Loops; [SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net70)] [MemoryDiagnoser(false)] public class Benchmarks { private int[] ItemsArray; private List<int> ItemsList; [GlobalSetup] public void Setup() { var random = new Random(420); var randomItems = Enumerable.Range(0, 1000).Select(_ => random.Next()); ItemsArray = randomItems.ToArray(); ItemsList = randomItems.ToList(); } [Benchmark] public void For_Array() { for (var i = 0; i < ItemsArray.Length; i++) { var item = ItemsArray[i]; } } [Benchmark] public void For_List() { for (var i = 0; i < ItemsList.Count; i++) { var item = ItemsList[i]; } } [Benchmark] public void ForEach_Array() { foreach (var item in ItemsArray) { } } [Benchmark] public void ForEach_List() { foreach (var item in ItemsList) { } } }
using BenchmarkDotNet.Running; using NET7Loops; BenchmarkRunner.Run<Benchmarks>();
- میتوان یک پروژه را یکبار بر اساس دات نت 7 و یکبار هم بر اساس دات نت 6 با تغییر target framework آنها کامپایل و اجرا کرد تا بتوان نتایج این دو را با هم مقایسه کرد و یا میتوان با ذکر [SimpleJob(RuntimeMoniker.Net60)] و همچنین [SimpleJob(RuntimeMoniker.Net70)]، این مورد را به صورت خودکار به BenchmarkDotNet دات نت واگذار کرد.
- در این آزمایش، ابتدا یک آرایه و یک لیست را تهیه میکنیم.
- سپس یکبار حلقههای for و foreach را بر روی آرایه و همین عملیات را بر روی لیست تهیه شده، تکرار میکنیم.
نتایج حاصل به صورت زیر هستند:
همانطور که در نتایج فوق هم مشاهده میکنید:
در دات نت 6
- تفاوتی بین کارآیی حلقههای for و foreach، زمانیکه بر روی یک آرایه اجرا میشوند، وجود ندارد.
- اما کارآیی حلقهی foreach نسبت به حلقهی for، زمانیکه بر روی یک لیست اجرا میشوند، تقریبا 50 درصد کمتر است.
در دات نت 7
- تفاوتی بین کارآیی حلقههای for و forach، زمانیکه بر روی یک آرایه اجرا میشوند، وجود ندارد. بنابراین از این لحاظ با دات نت 6 تفاوتی ندارد.
- اما کارآیی حلقهی foreach نسبت به حلقهی for، زمانیکه بر روی یک لیست اجرا میشود، تقریبا یکسان و قابل چشمپوشی است. یعنی در دات نت 7، کارآیی این دو حلقه یکی شدهاست. اما چرا؟
روشی در جهت یافتن یکی بودن سرعت حلقههای for و foreach بر اساس خروجی کامپایلر
با مشاهدهی نتایج حاصل از BenchmarkDotNet میتوان به بهبود کارآیی حاصل پیبرد؛ اما برای مثال چرا زمانیکه از آرایه استفاده میشود، حتی در دات نت 6، تفاوتی بین دو حلقهی for و foreach وجود ندارد، اما زمانیکه از لیستها استفاده میشود، این کارآیی 50 درصد افت میکند؟
برای پاسخ به این سؤال میتوان از IL Viewer موجود در Rider استفاده کرد که آخرین نگارش آن به همراه نمایش #Low-level C هم هست:
این همان خروجی است که توسط کامپایلر، پیش از تولید کدهای باینری نهایی، تهیه میشود. یعنی اگر قصد داشته باشیم تا درک کامپایلر را نسبت به قطعه کدی مشاهده کنیم، میتوان به این خروجی مراجعه کرد که به صورت زیر است:
// Decompiled with JetBrains decompiler // Type: NET7Loops.Benchmarks // Assembly: NET7Loops, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // MVID: E398BEE7-8123-4C55-AF9A-F7D83DDA73F1 // Assembly location: C:\Prog\1401\Net7Tests\NET7Loops\bin\Debug\net7.0\NET7Loops.dll // Compiler-generated code is shown using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace NET7Loops { [NullableContext(1)] [Nullable(0)] [SimpleJob(RuntimeMoniker.Net60, -1, -1, -1, -1, null, false)] [SimpleJob(RuntimeMoniker.Net70, -1, -1, -1, -1, null, false)] [MemoryDiagnoser(false)] public class Benchmarks { private int[] ItemsArray; private List<int> ItemsList; [GlobalSetup] public void Setup() { Benchmarks.<>c__DisplayClass2_0 cDisplayClass20 = new Benchmarks.<>c__DisplayClass2_0(); cDisplayClass20.random = new Random(420); IEnumerable<int> source = Enumerable.Range(0, 1000).Select<int, int>(new Func<int, int>((object) cDisplayClass20, __methodptr(<Setup>b__0))); this.ItemsArray = source.ToArray<int>(); this.ItemsList = source.ToList<int>(); } [Benchmark(23, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")] public void For_Array() { for (int index = 0; index < this.ItemsArray.Length; ++index) { int items = this.ItemsArray[index]; } } [Benchmark(32, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")] public void For_List() { for (int index = 0; index < this.ItemsList.Count; ++index) { int items = this.ItemsList[index]; } } [Benchmark(41, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")] public void ForEach_Array() { int[] itemsArray = this.ItemsArray; for (int index = 0; index < itemsArray.Length; ++index) { int num = itemsArray[index]; } } [Benchmark(49, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")] public void ForEach_List() { List<int>.Enumerator enumerator = this.ItemsList.GetEnumerator(); try { while (enumerator.MoveNext()) { int current = enumerator.Current; } } finally { enumerator.Dispose(); } } public Benchmarks() { base..ctor(); } [CompilerGenerated] private sealed class <>c__DisplayClass2_0 { [Nullable(0)] public Random random; public <>c__DisplayClass2_0() { base..ctor(); } internal int <Setup>b__0(int _) { return this.random.Next(); } } } }
for (int index = 0; index < this.ItemsArray.Length; ++index) { int items = this.ItemsArray[index]; }
List<int>.Enumerator enumerator = this.ItemsList.GetEnumerator(); try { while (enumerator.MoveNext()) { int current = enumerator.Current; } } finally { enumerator.Dispose(); }
دو پروژهی سورس باز XML RPC و Log4Net برای اجرا شدن در برنامههای دات نت 4 نیاز به اندکی تغییر در هر دو برنامهی فراخوان و اسمبلیهای آنها دارند که در ادامه توضیحات مربوطه ارائه خواهند شد.
اگر یک پروژهی جدید دات نت 4 را آغاز کنید و سپس ارجاعی را به یکی از اسمبلیهای ذکر شده اضافه نمائید، اولین خطایی را که حین استفاده مشاهده خواهید نمود، مورد زیر است:
Could not resolve assembly "System.Web".
The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client".
Please remove references to assemblies not in the targeted framework or consider retargeting your project.
خطای دومی که حین کار با کتابخانههای XML RPC و یا Log4Net در یک برنامهی دات نت 4 حتما با آن مواجه خواهید شد در ادامه ذکر گردیده است:
Inheritance security rules violated while overriding member:
GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext),
Security accessibility of the overriding method must match the security accessibility of the method being overriden.
برای رفع این مشکل ابتدا سورس این کتابخانهها را دریافت کرده و سپس در فایل AssemblyInfo.cs آنها یک سطر زیر را حذف نموده و پروژه را مجددا کامپایل کنید:
[assembly: AllowPartiallyTrustedCallers]
برای مطالعه بیشتر:
Using Libraries from Partially Trusted Code
Security Changes in the .NET Framework 4
TypeLoadException based on Security-Transparent Code, Level 2
Making log4net run on .NET 4.0