Use of the library does involve having an Apple Developer account at $99 a year , and to use the beta, developers will need to set up core Maps identifiers, keys, and tokens via their developer account. The beta version limits usage to 250,000 instantiations and 25,000 service calls, per API key per day, compared to Google's limit of 100,000 map instantiations per month on the free tier.
"SoReL-20M" (short for Sophos-ReversingLabs – 20 Million), as it's called, is a dataset containing metadata, labels, and features for 20 million Windows Portable Executable (.PE) files, including 10 million disarmed malware samples, with the goal of devising machine-learning approaches for better malware detection capabilities.
C# 11 is the next version of C# coming in .NET 7, and it is introducing a warning wave that issues a warning when a type is declared with all lower-case letters. This is being done so that in the future the language can begin moving away from conditional keywords and instead use full on keywords instead. The warning is alerting customers to types that may become keywords in future versions.
This is a defense of the most prolific and dedicated public servant that has graced the world in my lifetime. One man has added hundreds of billions, if not trillions of dollars of value to the global economy. This man has worked tirelessly for the benefit of everyone around him. It is impossible to name a publicly traded company that has not somehow benefited from his contributions, and many have benefited to the tune of billions. In return for the countless billions of wealth that people made from the fruits of his labor, he was rewarded with poverty and ridicule. Now that the world is done taking from him, they are heading to the next step of vilifying him as incompetent.
کتابخانه HTML5 Scratch Card
Scratch.js is a standalone library with no external dependencies which brings interactivity to your website by allowing you to generate scratch cards for your visitors.
Based on HTML5, scratch.js generate canvas on the fly and is optimized for all modern browsers and has touch support for mobile devices. You can use it to make scratch cards, coupons, promotionnal game and even advertisement.
using System.ComponentModel.DataAnnotations; namespace NotifyPropertyChangedGenerator.Demo; public enum Gender { NotSpecified, [Display(Name = "مرد")] Male, [Display(Name = "زن")] Female }
public static class Extensions { public static string GetDisplayName(this Enum value) { if (value is null) throw new ArgumentNullException(nameof(value)); var attribute = value.GetType().GetField(value.ToString())? .GetCustomAttributes<DisplayAttribute>(false).FirstOrDefault(); if (attribute is null) return value.ToString(); return attribute.GetType().GetProperty("Name")?.GetValue(attribute, null)?.ToString(); } }
یعنی در اصل متد کمکی که برای اینکار نیاز داریم، چنین خروجی را دارد:
namespace NotifyPropertyChangedGenerator.Demo { public static class GenderExtensions { public static string GetDisplayName(this Gender @enum) { return @enum switch { Gender.NotSpecified => "NotSpecified", Gender.Male => "مرد", Gender.Female => "زن", _ => throw new ArgumentOutOfRangeException(nameof(@enum)) }; } } }
با ارائهی Source Generators، مشکلات یاد شده دیگر وجود ندارند. یعنی کار تولید متدهای اختصاصی برای هر enum، خودکار است و همچنین به روز رسانی آنی آنها با هر تغییری در enumها نیز پیشبینی شدهاست.
تهیهی تولید کنندهی خودکار کدی که نام نمایشی enumها را به صورت از پیش محاسبه شده ارائه میدهد
در قسمت قبل، با روش تهیه و استفاده از Source Generators آشنا شدیم. در اینجا نیز از همان قالب، در جهت تولید کد متد الحاقی GetDisplayName فوق، استفاده خواهیم کرد. یعنی هدف رسیدن به کلاس GenderExtensions فوق و متد GetDisplayName آن، در زمان کامپایل برنامه و به صورت خودکار است:
[Generator] public class EnumExtensionsGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) {} public void Execute(GeneratorExecutionContext context) { var compilation = context.Compilation; foreach (var syntaxTree in compilation.SyntaxTrees) { var semanticModel = compilation.GetSemanticModel(syntaxTree); var immutableHashSet = syntaxTree.GetRoot() .DescendantNodesAndSelf() .OfType<EnumDeclarationSyntax>() .Select(enumDeclarationSyntax => semanticModel.GetDeclaredSymbol(enumDeclarationSyntax)) .OfType<ITypeSymbol>() /*.Where(typeSymbol => typeSymbol.GetAttributes().Any( attributeData => string.Equals(attributeData.AttributeClass?.Name, "GenerateExtensions", StringComparison.Ordinal) ))*/ .ToImmutableHashSet(); foreach (var typeSymbol in immutableHashSet) { var source = GenerateEnumExtensions(typeSymbol); context.AddSource($"{typeSymbol.Name}Extensions.cs", source); } } }
در اینجا در متد Execute، دسترسی کاملی را به اطلاعات تهیه شدهی توسط کامپایلر داریم. توسط آن تمام Enumهای برنامه را یا همان EnumDeclarationSyntax را در اینجا، یافته و سپس حلقهای را بر روی اطلاعات آنها تشکیل داده و برای تک تک آنها، توسط متد GenerateEnumExtensions، کد معادل کلاس GenderExtensions را که در این مطلب معرفی شد، تولید میکنیم. در پایان کار نیز این کد را توسط متد AddSource، به کامپایلر معرفی خواهیم کرد تا بلافاصله در IDE ظاهر شده و قابلیت استفاده را پیدا کند.
یک نکته: اگر میخواهید صرفا enumهای خاصی در این بین بررسی شوند، میتوانید کدهای یک Attribute سفارشی را مثلا با نام فرضی [GenerateExtensions] در همینجا توسط متد context.AddSource به مجموعه سورسها اضافه کنید و سپس بر اساس نام آن، در قسمت Where ایی که کامنت شدهاست، تنها اطلاعات مدنظر را فیلتر و پردازش کنید.
متدی هم که ابتدا کلاس Extensions را بر اساس نام هر Enum موجود تولید و سپس بدنهی متد GetDisplayName اختصاصی آنرا تکمیل میکند، به صورت زیر است:
private string GenerateEnumExtensions(ITypeSymbol typeSymbol) { return $@"namespace {typeSymbol.ContainingNamespace} {{ public static class {typeSymbol.Name}Extensions {{ public static string GetDisplayName(this {typeSymbol.Name} @enum) {{ {GenerateExtensionMethodBody(typeSymbol)} }} }} }}"; } private static string GenerateExtensionMethodBody(ITypeSymbol typeSymbol) { var sb = new StringBuilder(); sb.Append(@"return @enum switch { "); foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>()) { var displayAttribute = fieldSymbol.GetAttributes() .FirstOrDefault(attributeData => string.Equals(attributeData.AttributeClass?.Name, "DisplayAttribute", StringComparison.Ordinal)); if (displayAttribute is null) { sb.AppendLine( $@" {typeSymbol.Name}.{fieldSymbol.Name} => ""{fieldSymbol.Name}"","); } else { var displayAttributeName = displayAttribute.NamedArguments .FirstOrDefault(x => string.Equals(x.Key, "Name", StringComparison.Ordinal)) .Value; sb.AppendLine( $@" {typeSymbol.Name}.{fieldSymbol.Name} => ""{displayAttributeName.Value}"","); } } sb.Append( @" _ => throw new ArgumentOutOfRangeException(nameof(@enum)) };"); return sb.ToString(); }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SourceGeneratorTests-part3.zip
سؤال: چگونه میتوان کدهای تولید شدهی توسط یک Source Generator را ذخیره کرد؟
Source Generators به صورت پیشفرض هیچ فایلی را بر روی دیسک سخت ذخیره نمیکنند و تمام عملیات آنها در حافظه انجام میشود. اگر علاقمند به مطالعهی این خروجیهای خودکار، به صورت فایلهای واقعی هستید، نیاز به انجام تغییرات زیر در فایل csproj پروژهی مصرف کنندهی Source Generator است:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath> </PropertyGroup> <Target Name="CleanSourceGeneratedFiles" BeforeTargets="BeforeBuild" DependsOnTargets="$(BeforeBuildDependsOn)"> <RemoveDir Directories="$(CompilerGeneratedFilesOutputPath)" /> </Target> <ItemGroup> <!-- Exclude the output of source generators from the compilation --> <Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" /> <Content Include="$(CompilerGeneratedFilesOutputPath)/**" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> </Project>
- EmitCompilerGeneratedFiles سبب ثبت فایلهای خودکار تولید شده، بر روی دیسک سخت میشود که قالب مسیر پیشفرض ذخیره سازی آن به صورت زیر است:
{BaseIntermediateOutpath}/generated/{Assembly}/{SourceGeneratorName}/{GeneratedFile}
- چون این فایلهای cs. جدید ثبت شدهی بر روی دیسک سخت، مجددا وارد پروسهی کامپایل میشوند و خود Source Generator هم یک نمونهی از آنهارا پیشتر به کامپایلر معرفی کردهاست، برنامه دیگر به علت وجود اطلاعات تکراری، کامپایل نخواهد شد. به همین جهت نیاز است تا قسمت Compile Remove فوق را نیز معرفی کرد تا کامپایلر از پوشهی Generated تنظیمی، صرفنظر کند.
- اطلاعات موجود در پوشهی Generated، فقط یکبار تولید میشوند؛ صرفنظر از اطلاعات موجود در حافظه که همیشه به روز است. به همین جهت اگر میخواهید نمونههای به روز شدهی آنها را نیز بر روی دیسک سخت داشته باشید، نیاز به قسمت RemoveDir تنظیمی وجود دارد.