diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 7cd14ce..7d19ce4 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -61,14 +61,14 @@ jobs: shell: pwsh - name: Create NuGet Package - # run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 1.1.4 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + # run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.17 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} run: nuget pack ThunderDesign.Net-PCL.nuspec -Version ${{ github.event.release.tag_name }} -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} - name: Archive NuGet Package uses: actions/upload-artifact@v4 with: - # name: Package_${{ env.FILE_NAME}}.1.1.4 - # path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.1.1.4.nupkg + # name: Package_${{ env.FILE_NAME}}.2.0.17 + # path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.17.nupkg name: Package_${{ env.FILE_NAME}}.${{ github.event.release.tag_name }} path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg diff --git a/README.md b/README.md index 0a2e1ba..224a196 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ThunderDesign.Net-PCL.Threading +# ThunderDesign.Net-PCL.Threading [![CI](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.Threading/actions/workflows/CI.yml/badge.svg)](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.Threading/actions/workflows/CI.yml) [![CD](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.Threading/actions/workflows/CD.yml/badge.svg)](https://github.com/ThunderDesign/ThunderDesign.Net-PCL.Threading/actions/workflows/CD.yml) [![Nuget](https://img.shields.io/nuget/v/ThunderDesign.Net-PCL.Threading)](https://www.nuget.org/packages/ThunderDesign.Net-PCL.Threading) @@ -107,9 +107,10 @@ With the source generator, you only need to annotate your fields: ```csharp using ThunderDesign.Net.Threading.Attributes; + public partial class Person -{ - [BindableProperty] +{ + [BindableProperty] private string _name; [Property] @@ -117,9 +118,7 @@ public partial class Person } ``` -**What gets generated:** -- A public `Name` property with thread-safe getter/setter and `INotifyPropertyChanged` support. -- A public `Age` property with thread-safe getter/setter. +**What gets generated:** ```csharp using System.ComponentModel; @@ -130,7 +129,6 @@ using ThunderDesign.Net.Threading.Interfaces; public partial class Person : IBindableObject, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; - protected readonly object _Locker = new object(); public string Name @@ -150,18 +148,132 @@ public partial class Person : IBindableObject, INotifyPropertyChanged You can now use your `Person` class like this: ```csharp -var person = new Person(); -person.Name = "Alice"; -person.Age = 30; // PropertyChanged event will be raised for Name changes if you subscribe to it. +var person = new Person(); +person.Name = "Alice"; +person.Age = 30; +// PropertyChanged event will be raised for Name changes if you subscribe to it. ``` - **No need to manually implement** property notification, thread safety, or boilerplate code—the generator does it for you! -> For more advanced scenarios, you can use attribute parameters to control property behavior (e.g., read-only, also notify other properties, etc.). +> For more advanced scenarios, you can use attribute parameters to control property behavior (e.g., read-only, also notify other properties, or control accessor/property visibility). +--- ----- +### Advanced: Customizing Getter and Setter Accessors + +You can control the visibility of the generated property's getter and setter using the `AccessorAccessibility` enum. +The property itself will use the most accessible (widest) of the getter or setter's accessibilities. + +#### Example + +```csharp +using ThunderDesign.Net.Threading.Attributes; +using ThunderDesign.Net.Threading.Enums; + +public partial class Person +{ + // Public getter, private setter (property will be public) + [BindableProperty(getter: AccessorAccessibility.Public, setter: AccessorAccessibility.Private)] + private string _name; + + // Internal getter, protected setter (property will be internal) + [Property(getter: AccessorAccessibility.Internal, setter: AccessorAccessibility.Protected)] + private int _age; +} +``` + +**What gets generated:** +```csharp +public partial class Person +{ + public string Name + { + get { return this.GetProperty(ref _name, _Locker); } + private set { this.SetProperty(ref _name, value, _Locker, true); } + } + + internal int Age + { + internal get { return this.GetProperty(ref _age, _Locker); } + protected set { this.SetProperty(ref _age, value, _Locker); } + } +} + +``` + +> The property will be as accessible as its most accessible accessor (getter or setter). +> The default for `getter`, and `setter` is `public` if not specified. + +**Available options for `AccessorAccessibility`:** +- `Public` +- `Private` +- `Protected` +- `Internal` +- `ProtectedInternal` +- `PrivateProtected` + +--- + +### Advanced: Notify Other Properties + +You can notify other properties when a specific property changes by using the `alsoNotify` parameter in the `[BindableProperty]` attribute. + +#### Example +```csharp +using ThunderDesign.Net.Threading.Attributes; + +public partial class Person +{ + [BindableProperty(alsoNotify: new[] { nameof(DisplayName) })] + private string _firstName; + + [BindableProperty(alsoNotify: new[] { nameof(DisplayName) })] + private string _lastName; + + public string DisplayName => $"{FirstName} {LastName}"; +} +``` + +**What gets generated:** + +```csharp +public partial class Person : IBindableObject, INotifyPropertyChanged +{ + public event PropertyChangedEventHandler PropertyChanged; + protected readonly object _Locker = new object(); + + public string FirstName + { + get { return this.GetProperty(ref _firstName, _Locker); } + set + { + if (this.SetProperty(ref _firstName, value, _Locker, true)) + { + this.OnPropertyChanged(nameof(DisplayName)); + } + } + } + + public string LastName + { + get { return this.GetProperty(ref _lastName, _Locker); } + set + { + if (this.SetProperty(ref _lastName, value, _Locker, true)) + { + this.OnPropertyChanged(nameof(DisplayName)); + } + } + } + + public string DisplayName => $"{FirstName} {LastName}"; +} +``` + +> This feature is particularly useful for computed properties like `DisplayName` that depend on other properties. + +--- ## Installation @@ -199,4 +311,6 @@ This can be overwritten durring creation or by setting Property `WaitOnNotifyCol Observable Objects Property `WaitOnNotifyPropertyChanged` has been renamed to Property `WaitOnNotifying`. Observable Collections Property `WaitOnNotifyCollectionChanged` has been removed and now uses Property `WaitOnNotifying`. ----- \ No newline at end of file +---- + + diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/PropertyGeneratorHelpers.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/Helpers/PropertyGeneratorHelpers.cs similarity index 95% rename from src/ThunderDesign.Net-PCL.SourceGenerators/PropertyGeneratorHelpers.cs rename to src/ThunderDesign.Net-PCL.SourceGenerators/Helpers/PropertyGeneratorHelpers.cs index e51d800..a71f7da 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/PropertyGeneratorHelpers.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/Helpers/PropertyGeneratorHelpers.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; -namespace ThunderDesign.Net_PCL.SourceGenerators +namespace ThunderDesign.Net.SourceGenerators.Helpers { internal static class PropertyGeneratorHelpers { @@ -74,7 +74,7 @@ public static PropertyFieldInfo GetFieldWithAttribute(GeneratorSyntaxContext con } } } - return default(PropertyFieldInfo); + return default; } // Rule 2: Field must start with "_" followed by a letter, or a lowercase letter @@ -123,17 +123,17 @@ public static bool EventExists(INamedTypeSymbol classSymbol, string eventName, I public static bool MethodExists( INamedTypeSymbol classSymbol, string methodName, - ITypeSymbol[]? parameterTypes = null, - ITypeSymbol? returnType = null) + ITypeSymbol[] parameterTypes = null, + ITypeSymbol returnType = null) { return classSymbol.GetMembers() .OfType() .Any(m => m.Name == methodName && (parameterTypes == null || - (m.Parameters.Length == parameterTypes.Length && + m.Parameters.Length == parameterTypes.Length && m.Parameters.Select(p => p.Type.ToDisplayString()) - .SequenceEqual(parameterTypes.Select(t => t.ToDisplayString())))) && + .SequenceEqual(parameterTypes.Select(t => t.ToDisplayString()))) && (returnType == null || SymbolEqualityComparer.Default.Equals(m.ReturnType, returnType)) ); } diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/ThunderDesign.Net-PCL.SourceGenerators.csproj b/src/ThunderDesign.Net-PCL.SourceGenerators/ThunderDesign.Net-PCL.SourceGenerators.csproj index d7a497e..40e861c 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/ThunderDesign.Net-PCL.SourceGenerators.csproj +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/ThunderDesign.Net-PCL.SourceGenerators.csproj @@ -4,7 +4,7 @@ netstandard2.0 latest true - ThunderDesign.Net_PCL.SourceGenerators + ThunderDesign.Net.SourceGenerators false true diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index bcb9796..bbeadd0 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -2,16 +2,50 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Text; +using System.Threading; +using ThunderDesign.Net.SourceGenerators.Helpers; -namespace ThunderDesign.Net_PCL.SourceGenerators +namespace ThunderDesign.Net.SourceGenerators { [Generator] public class UnifiedPropertyGenerator : IIncrementalGenerator { + private static readonly Dictionary AccessibilityNameMap = new Dictionary + { + { 0, "Public" }, + { 1, "Private" }, + { 2, "Protected" }, + { 3, "Internal" }, + { 4, "ProtectedInternal" }, + { 5, "PrivateProtected" } + }; + + private static readonly Dictionary AccessibilityRankMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Public", 6 }, + { "ProtectedInternal", 5 }, + { "Internal", 4 }, + { "Protected", 3 }, + { "PrivateProtected", 2 }, + { "Private", 1 } + }; + + private static readonly Dictionary AccessibilityKeywordMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Public", "public " }, + { "Private", "private " }, + { "Protected", "protected " }, + { "Internal", "internal " }, + { "ProtectedInternal", "protected internal " }, + { "PrivateProtected", "private protected " } + }; + public void Initialize(IncrementalGeneratorInitializationContext context) { //if (!Debugger.IsAttached) @@ -20,48 +54,56 @@ public void Initialize(IncrementalGeneratorInitializationContext context) //} // Collect all fields with [BindableProperty] or [Property] var fieldsWithAttribute = context.SyntaxProvider - .CreateSyntaxProvider( + .CreateSyntaxProvider<(INamedTypeSymbol Class, BindableFieldInfo Bindable, PropertyFieldInfo Property)>( predicate: static (node, _) => node is FieldDeclarationSyntax fds && fds.AttributeLists.Count > 0, - transform: static (ctx, _) => - { - var bindable = GetBindableField(ctx); - if (!bindable.Equals(default(BindableFieldInfo))) - return (Class: bindable.ContainingClass, Bindable: bindable, Property: default(PropertyFieldInfo)); - var prop = PropertyGeneratorHelpers.GetFieldWithAttribute(ctx, "PropertyAttribute"); - if (!prop.Equals(default(PropertyFieldInfo))) - return (Class: prop.ContainingClass, Bindable: default(BindableFieldInfo), Property: prop); - return default; - } + transform: GetFieldInfos ) .Where(static info => !info.Equals(default((INamedTypeSymbol, BindableFieldInfo, PropertyFieldInfo)))); - // Group by class + // Group by class - use the original approach which was working before var grouped = fieldsWithAttribute.Collect() .Select((list, _) => list .Where(info => info.Class is INamedTypeSymbol) .GroupBy(info => info.Class, SymbolEqualityComparer.Default) .Select(g => ( - ClassSymbol: g.Key, + ClassSymbol: g.Key as INamedTypeSymbol, // Explicit cast to INamedTypeSymbol BindableFields: g.Select(x => x.Bindable).Where(b => !b.Equals(default(BindableFieldInfo))).ToList(), PropertyFields: g.Select(x => x.Property).Where(p => !p.Equals(default(PropertyFieldInfo))).ToList() )) .ToList() ); - var compilationProvider = context.CompilationProvider; + context.RegisterSourceOutput( + grouped.Combine(context.CompilationProvider), + GenerateSourceCode + ); + } - context.RegisterSourceOutput(grouped.Combine(compilationProvider), (spc, tuple) => + private static (INamedTypeSymbol Class, BindableFieldInfo Bindable, PropertyFieldInfo Property) GetFieldInfos(GeneratorSyntaxContext ctx, CancellationToken _) + { + var bindable = GetBindableField(ctx); + if (!bindable.Equals(default(BindableFieldInfo))) + return (Class: bindable.ContainingClass, Bindable: bindable, Property: default(PropertyFieldInfo)); + + var prop = PropertyGeneratorHelpers.GetFieldWithAttribute(ctx, "PropertyAttribute"); + if (!prop.Equals(default(PropertyFieldInfo))) + return (Class: prop.ContainingClass, Bindable: default(BindableFieldInfo), Property: prop); + + return default; + } + + private static void GenerateSourceCode(SourceProductionContext spc, + (List<(INamedTypeSymbol ClassSymbol, List BindableFields, List PropertyFields)> Left, + Compilation Right) tuple) + { + var (classGroups, compilation) = tuple; + foreach (var group in classGroups) { - var (classGroups, compilation) = (tuple.Left, tuple.Right); - foreach (var group in classGroups) + if (group.ClassSymbol != null) { - var classSymbol = group.ClassSymbol as INamedTypeSymbol; - if (classSymbol != null) - { - GenerateUnifiedPropertyClass(spc, classSymbol, group.BindableFields, group.PropertyFields, compilation); - } + GenerateUnifiedPropertyClass(spc, group.ClassSymbol, group.BindableFields, group.PropertyFields, compilation); } - }); + } } private static BindableFieldInfo GetBindableField(GeneratorSyntaxContext context) @@ -78,11 +120,10 @@ private static BindableFieldInfo GetBindableField(GeneratorSyntaxContext context { if (attr.AttributeClass?.Name == "BindablePropertyAttribute") { - var containingClass = fieldSymbol.ContainingType; return new BindableFieldInfo { FieldSymbol = fieldSymbol, - ContainingClass = containingClass, + ContainingClass = fieldSymbol.ContainingType, AttributeData = attr, FieldDeclaration = fieldDecl }; @@ -90,7 +131,7 @@ private static BindableFieldInfo GetBindableField(GeneratorSyntaxContext context } } } - return default(BindableFieldInfo); + return default; } private static void GenerateUnifiedPropertyClass( @@ -100,6 +141,9 @@ private static void GenerateUnifiedPropertyClass( List propertyFields, Compilation compilation) { + if (!ValidateFields(context, classSymbol, bindableFields, propertyFields)) + return; + var implementsINotify = ImplementsInterface(classSymbol, "System.ComponentModel.INotifyPropertyChanged"); var implementsIBindable = ImplementsInterface(classSymbol, "ThunderDesign.Net.Threading.Interfaces.IBindableObject"); var inheritsThreadObject = PropertyGeneratorHelpers.InheritsFrom(classSymbol, "ThunderDesign.Net.Threading.Objects.ThreadObject"); @@ -108,83 +152,148 @@ private static void GenerateUnifiedPropertyClass( var voidTypeSymbol = compilation.GetSpecialType(SpecialType.System_Void); var propertyChangedEventType = compilation.GetTypeByMetadataName("System.ComponentModel.PropertyChangedEventHandler"); - // --- RULE CHECKS FOR BINDABLE FIELDS --- + var source = new StringBuilder(); + GenerateClassHeader(source, classSymbol, bindableFields, implementsIBindable); + + // Add infrastructure members if needed + GenerateInfrastructureMembers( + source, + bindableFields, + implementsINotify, + implementsIBindable, + inheritsThreadObject, + classSymbol, + propertyChangedEventType, + stringTypeSymbol, + voidTypeSymbol); + + // Generate properties + GenerateBindableProperties(source, bindableFields, classSymbol); + GenerateRegularProperties(source, propertyFields, classSymbol); + + source.AppendLine("}"); + if (!string.IsNullOrEmpty(classSymbol.ContainingNamespace?.ToDisplayString())) + source.AppendLine("}"); + + // Generate unique filename + var safeClassName = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + .Replace(".", "_") + .Replace("global::", ""); + var hintName = $"{safeClassName}_AllProperties.g.cs"; + + context.AddSource(hintName, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static bool ValidateFields( + SourceProductionContext context, + INamedTypeSymbol classSymbol, + List bindableFields, + List propertyFields) + { + // Check bindable fields foreach (var info in bindableFields) { - // Rule 1: Class must be partial if (!PropertyGeneratorHelpers.IsPartial(classSymbol)) { - PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Class '{classSymbol.Name}' must be partial to use [BindableProperty]."); - continue; + PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), + $"Class '{classSymbol.Name}' must be partial to use [BindableProperty]."); + return false; } - // Rule 2: Field must start with "_" or lowercase + if (!PropertyGeneratorHelpers.IsValidFieldName(info.FieldSymbol.Name)) { - PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Field '{info.FieldSymbol.Name}' must start with '_' or a lowercase letter to use [BindableProperty]."); - continue; + PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), + $"Field '{info.FieldSymbol.Name}' must start with '_' or a lowercase letter to use [BindableProperty]."); + return false; } - // Rule 3: Property must not already exist + var propertyName = PropertyGeneratorHelpers.ToPropertyName(info.FieldSymbol.Name); if (PropertyGeneratorHelpers.PropertyExists(classSymbol, propertyName)) { - PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Property '{propertyName}' already exists in '{classSymbol.Name}'."); - continue; + PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), + $"Property '{propertyName}' already exists in '{classSymbol.Name}'."); + return false; } } - // --- RULE CHECKS FOR PROPERTY FIELDS --- + // Check property fields foreach (var info in propertyFields) { - // Rule 1: Class must be partial if (!PropertyGeneratorHelpers.IsPartial(classSymbol)) { - PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Class '{classSymbol.Name}' must be partial to use [Property]."); - continue; + PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), + $"Class '{classSymbol.Name}' must be partial to use [Property]."); + return false; } - // Rule 2: Field must start with "_" or lowercase + if (!PropertyGeneratorHelpers.IsValidFieldName(info.FieldSymbol.Name)) { - PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Field '{info.FieldSymbol.Name}' must start with '_' or a lowercase letter to use [Property]."); - continue; + PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), + $"Field '{info.FieldSymbol.Name}' must start with '_' or a lowercase letter to use [Property]."); + return false; } - // Rule 3: Property must not already exist + var propertyName = PropertyGeneratorHelpers.ToPropertyName(info.FieldSymbol.Name); if (PropertyGeneratorHelpers.PropertyExists(classSymbol, propertyName)) { - PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Property '{propertyName}' already exists in '{classSymbol.Name}'."); - continue; + PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), + $"Property '{propertyName}' already exists in '{classSymbol.Name}'."); + return false; } } + + return true; + } - var source = new StringBuilder(); - var ns = classSymbol.ContainingNamespace.IsGlobalNamespace ? null : classSymbol.ContainingNamespace.ToDisplayString(); - + private static void GenerateClassHeader( + StringBuilder source, + INamedTypeSymbol classSymbol, + List bindableFields, + bool implementsIBindable) + { + var ns = classSymbol.ContainingNamespace?.ToDisplayString(); + if (!string.IsNullOrEmpty(ns)) source.AppendLine($"namespace {ns} {{"); source.AppendLine("using ThunderDesign.Net.Threading.Extentions;"); source.AppendLine("using ThunderDesign.Net.Threading.Objects;"); + if (bindableFields.Count > 0) - { source.AppendLine("using ThunderDesign.Net.Threading.Interfaces;"); - } source.Append($"partial class {classSymbol.Name}"); - var interfaces = new List(); + if (bindableFields.Count > 0 && !implementsIBindable) - interfaces.Add("IBindableObject"); - if (interfaces.Count > 0) - source.Append(" : " + string.Join(", ", interfaces)); + source.Append(" : IBindableObject"); + source.AppendLine(); source.AppendLine("{"); + } + private static void GenerateInfrastructureMembers( + StringBuilder source, + List bindableFields, + bool implementsINotify, + bool implementsIBindable, + bool inheritsThreadObject, + INamedTypeSymbol classSymbol, + INamedTypeSymbol propertyChangedEventType, + ITypeSymbol stringTypeSymbol, + ITypeSymbol voidTypeSymbol) + { // Add event if needed - if (bindableFields.Count > 0 && !implementsINotify && !PropertyGeneratorHelpers.EventExists(classSymbol, "PropertyChanged", propertyChangedEventType)) + if (bindableFields.Count > 0 && !implementsINotify && + !PropertyGeneratorHelpers.EventExists(classSymbol, "PropertyChanged", propertyChangedEventType)) + { source.AppendLine(" public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); + } // Add _Locker if needed if ((!inheritsThreadObject) && !PropertyGeneratorHelpers.FieldExists(classSymbol, "_Locker")) + { source.AppendLine(" protected readonly object _Locker = new object();"); + } // Add OnPropertyChanged if needed if (bindableFields.Count > 0 && !implementsIBindable && !PropertyGeneratorHelpers.MethodExists( @@ -199,11 +308,15 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem this.NotifyPropertyChanged(PropertyChanged, propertyName); }"); } + } - // Generate all bindable properties + private static void GenerateBindableProperties( + StringBuilder source, + List bindableFields, + INamedTypeSymbol classSymbol) + { foreach (var info in bindableFields) { - // Skip if any rule failed (diagnostic already reported) var propertyName = PropertyGeneratorHelpers.ToPropertyName(info.FieldSymbol.Name); if (!PropertyGeneratorHelpers.IsPartial(classSymbol) || !PropertyGeneratorHelpers.IsValidFieldName(info.FieldSymbol.Name) || @@ -216,72 +329,155 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem var fieldName = fieldSymbol.Name; var typeName = fieldSymbol.Type.ToDisplayString(); - var readOnly = info.AttributeData.ConstructorArguments.Length > 0 && (bool)info.AttributeData.ConstructorArguments[0].Value!; - var threadSafe = info.AttributeData.ConstructorArguments.Length > 1 && (bool)info.AttributeData.ConstructorArguments[1].Value!; - var notify = info.AttributeData.ConstructorArguments.Length > 2 && (bool)info.AttributeData.ConstructorArguments[2].Value!; - string[] alsoNotify = null; - if (info.AttributeData.ConstructorArguments.Length > 3) - { - var arg = info.AttributeData.ConstructorArguments[3]; - if (arg.Kind == TypedConstantKind.Array && arg.Values != null) - { - alsoNotify = arg.Values - .Select(tc => tc.Value as string) - .Where(s => !string.IsNullOrEmpty(s)) - .ToArray(); - } - } - if (alsoNotify == null) - alsoNotify = new string[0]; + var args = info.AttributeData.ConstructorArguments; + var readOnly = args.Length > 0 && (bool)args[0].Value!; + var threadSafe = args.Length > 1 && (bool)args[1].Value!; + var notify = args.Length > 2 && (bool)args[2].Value!; + + string[] alsoNotify = GetAlsoNotifyProperties(args); + + var getterEnum = args.Length > 4 ? args[4].Value : null; + var setterEnum = args.Length > 5 ? args[5].Value : null; + // Convert the numeric enum value to its string representation + string getterValue = getterEnum != null ? GetAccessibilityName((int)getterEnum) : "Public"; + string setterValue = setterEnum != null ? GetAccessibilityName((int)setterEnum) : "Public"; + + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); + var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; + if (readOnly) { - source.AppendLine($@" - public {typeName} {propertyName} - {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - }}"); + GenerateReadOnlyBindableProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg); } else { - string setAccessor; - if (alsoNotify.Length > 0) - { - var notifyCalls = new StringBuilder(); - foreach (var prop in alsoNotify) - { - if (!string.IsNullOrEmpty(prop)) - notifyCalls.AppendLine($" this.OnPropertyChanged(\"{prop}\");"); - } - setAccessor = $@" - set + GenerateReadWriteBindableProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg, + notifyArg, + alsoNotify, + setterValue); + } + } + } + + private static string[] GetAlsoNotifyProperties(ImmutableArray args) + { + if (args.Length <= 3) + return Array.Empty(); + + var arg = args[3]; + if (arg.Kind == TypedConstantKind.Array && arg.Values != null) + { + return arg.Values + .Select(tc => tc.Value as string) + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + } + + return Array.Empty(); + } + + private static void GenerateReadOnlyBindableProperty( + StringBuilder source, + string propertyAccessibilityStr, + string typeName, + string propertyName, + string getterValue, + string propertyAccessRaw, + string fieldName, + string lockerArg) + { + string getterModifier = getterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue); + + source.AppendLine($@" + {propertyAccessibilityStr}{typeName} {propertyName} + {{ + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + }}"); + } + + private static void GenerateReadWriteBindableProperty( + StringBuilder source, + string propertyAccessibilityStr, + string typeName, + string propertyName, + string getterValue, + string propertyAccessRaw, + string fieldName, + string lockerArg, + string notifyArg, + string[] alsoNotify, + string setterValue) + { + string getterModifier = getterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue); + + string setterModifier = setterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue); + + if (alsoNotify.Length > 0) + { + var notifyCalls = new StringBuilder(); + foreach (var prop in alsoNotify) + { + if (!string.IsNullOrEmpty(prop)) + notifyCalls.AppendLine($" this.OnPropertyChanged(\"{prop}\");"); + } + + source.AppendLine($@" + {propertyAccessibilityStr}{typeName} {propertyName} + {{ + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {setterModifier}set {{ if (this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg})) {{ {notifyCalls.ToString().TrimEnd()} }} - }}"; - } - else - { - setAccessor = $"set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }}"; - } - - source.AppendLine($@" - public {typeName} {propertyName} + }} + }}"); + } + else + { + source.AppendLine($@" + {propertyAccessibilityStr}{typeName} {propertyName} {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - {setAccessor} + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {setterModifier}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }} }}"); - } } + } - // Generate all regular properties + private static void GenerateRegularProperties( + StringBuilder source, + List propertyFields, + INamedTypeSymbol classSymbol) + { foreach (var info in propertyFields) { - // Skip if any rule failed (diagnostic already reported) var propertyName = PropertyGeneratorHelpers.ToPropertyName(info.FieldSymbol.Name); if (!PropertyGeneratorHelpers.IsPartial(classSymbol) || !PropertyGeneratorHelpers.IsValidFieldName(info.FieldSymbol.Name) || @@ -294,41 +490,95 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem var fieldName = fieldSymbol.Name; var typeName = fieldSymbol.Type.ToDisplayString(); - var readOnly = info.AttributeData.ConstructorArguments.Length > 0 && (bool)info.AttributeData.ConstructorArguments[0].Value!; - var threadSafe = info.AttributeData.ConstructorArguments.Length > 1 && (bool)info.AttributeData.ConstructorArguments[1].Value!; + var args = info.AttributeData.ConstructorArguments; + var readOnly = args.Length > 0 && (bool)args[0].Value!; + var threadSafe = args.Length > 1 && (bool)args[1].Value!; + var getterEnum = args.Length > 2 ? args[2].Value : null; + var setterEnum = args.Length > 3 ? args[3].Value : null; + + // Convert the numeric enum value to its string representation + string getterValue = getterEnum != null ? GetAccessibilityName((int)getterEnum) : "Public"; + string setterValue = setterEnum != null ? GetAccessibilityName((int)setterEnum) : "Public"; + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); + var lockerArg = threadSafe ? "_Locker" : "null"; + if (readOnly) { - source.AppendLine($@" - public {typeName} {propertyName} - {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - }}"); + GenerateReadOnlyProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg); } else { - source.AppendLine($@" - public {typeName} {propertyName} - {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} - }}"); + GenerateReadWriteProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg, + setterValue); } } + } - source.AppendLine("}"); + private static void GenerateReadOnlyProperty( + StringBuilder source, + string propertyAccessibilityStr, + string typeName, + string propertyName, + string getterValue, + string propertyAccessRaw, + string fieldName, + string lockerArg) + { + string getterModifier = getterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue); - if (!string.IsNullOrEmpty(ns)) - source.AppendLine("}"); + source.AppendLine($@" + {propertyAccessibilityStr}{typeName} {propertyName} + {{ + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + }}"); + } - // Ensure unique hintName by including the full metadata name - var safeClassName = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - .Replace(".", "_") - .Replace("global::", ""); - var hintName = $"{safeClassName}_AllProperties.g.cs"; + private static void GenerateReadWriteProperty( + StringBuilder source, + string propertyAccessibilityStr, + string typeName, + string propertyName, + string getterValue, + string propertyAccessRaw, + string fieldName, + string lockerArg, + string setterValue) + { + string getterModifier = getterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue); - context.AddSource(hintName, SourceText.From(source.ToString(), Encoding.UTF8)); + string setterModifier = setterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue); + + source.AppendLine($@" + {propertyAccessibilityStr}{typeName} {propertyName} + {{ + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {setterModifier}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} + }}"); } private static bool ImplementsInterface(INamedTypeSymbol type, string interfaceName) @@ -336,6 +586,23 @@ private static bool ImplementsInterface(INamedTypeSymbol type, string interfaceN return type.AllInterfaces.Any(i => i.ToDisplayString() == interfaceName); } + private static string GetAccessibilityName(int value) + { + return AccessibilityNameMap.TryGetValue(value, out string name) ? name : "Public"; + } + + private static string ToPropertyAccessibilityString(string access) + { + return AccessibilityKeywordMap.TryGetValue(access, out string keyword) ? keyword : "public "; + } + + private static string GetWidestAccessibility(string getter, string setter) + { + int getterRank = AccessibilityRankMap.TryGetValue(getter, out int gRank) ? gRank : 0; + int setterRank = AccessibilityRankMap.TryGetValue(setter, out int sRank) ? sRank : 0; + return getterRank >= setterRank ? getter : setter; + } + private struct BindableFieldInfo { public IFieldSymbol FieldSymbol { get; set; } diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs new file mode 100644 index 0000000..133f952 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ThunderDesign.Net.Threading.Enums; + +namespace ThunderDesign.Net.Threading.Attributes +{ +#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + public sealed class BindablePropertyAttribute : Attribute + { + public bool ReadOnly { get; } + public bool ThreadSafe { get; } + public bool Notify { get; } + public string[] AlsoNotify { get; } + public AccessorAccessibility Getter { get; } + public AccessorAccessibility Setter { get; } + + public BindablePropertyAttribute( + bool readOnly = false, + bool threadSafe = true, + bool notify = true, + string[] alsoNotify = null, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public) + { + ReadOnly = readOnly; + ThreadSafe = threadSafe; + Notify = notify; + AlsoNotify = alsoNotify ?? Array.Empty(); + Getter = getter; + Setter = setter; + } + } +#endif +} \ No newline at end of file diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs new file mode 100644 index 0000000..e544d01 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs @@ -0,0 +1,28 @@ +using System; +using ThunderDesign.Net.Threading.Enums; + +namespace ThunderDesign.Net.Threading.Attributes +{ +#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + public sealed class PropertyAttribute : Attribute + { + public bool ReadOnly { get; } + public bool ThreadSafe { get; } + public AccessorAccessibility Getter { get; } + public AccessorAccessibility Setter { get; } + + public PropertyAttribute( + bool readOnly = false, + bool threadSafe = true, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public) + { + ReadOnly = readOnly; + ThreadSafe = threadSafe; + Getter = getter; + Setter = setter; + } + } +#endif +} diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs new file mode 100644 index 0000000..924cee4 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs @@ -0,0 +1,14 @@ +namespace ThunderDesign.Net.Threading.Enums +{ +#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER + public enum AccessorAccessibility + { + Public, + Private, + Protected, + Internal, + ProtectedInternal, + PrivateProtected + } +#endif +} \ No newline at end of file diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/ThunderDesign.Net-PCL.Threading.Shared.csproj b/src/ThunderDesign.Net-PCL.Threading.Shared/ThunderDesign.Net-PCL.Threading.Shared.csproj index c63318e..c06235c 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/ThunderDesign.Net-PCL.Threading.Shared.csproj +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/ThunderDesign.Net-PCL.Threading.Shared.csproj @@ -2,7 +2,7 @@ netstandard1.0;netstandard1.3;netstandard2.0;net461;net6.0;net8.0 - ThunderDesign.Net_PCL.Threading.Shared + ThunderDesign.Net.Threading.Shared true true true diff --git a/src/ThunderDesign.Net-PCL.Threading/Attributes/AlsoNotify.cs b/src/ThunderDesign.Net-PCL.Threading/Attributes/AlsoNotify.cs deleted file mode 100644 index 9c59699..0000000 --- a/src/ThunderDesign.Net-PCL.Threading/Attributes/AlsoNotify.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace ThunderDesign.Net_PCL.Threading.Attributes -{ - [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)] - public sealed class AlsoNotifyAttribute : Attribute - { - public string PropertyName { get; } - - public AlsoNotifyAttribute(string propertyName) - { - PropertyName = propertyName; - } - } -} \ No newline at end of file diff --git a/src/ThunderDesign.Net-PCL.Threading/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading/Attributes/BindablePropertyAttribute.cs deleted file mode 100644 index de464d1..0000000 --- a/src/ThunderDesign.Net-PCL.Threading/Attributes/BindablePropertyAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace ThunderDesign.Net_PCL.Threading.Attributes -{ - [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] - public sealed class BindablePropertyAttribute : Attribute - { - public bool ReadOnly { get; } - public bool ThreadSafe { get; } - public bool Notify { get; } - public string[] AlsoNotify { get; } - - public BindablePropertyAttribute( - bool readOnly = false, - bool threadSafe = true, - bool notify = true, - string[] alsoNotify = null) - { - ReadOnly = readOnly; - ThreadSafe = threadSafe; - Notify = notify; - AlsoNotify = alsoNotify ?? new string[0]; - } - } -} \ No newline at end of file diff --git a/src/ThunderDesign.Net-PCL.Threading/Attributes/INotifyPropertyChangedAttribute.cs b/src/ThunderDesign.Net-PCL.Threading/Attributes/INotifyPropertyChangedAttribute.cs deleted file mode 100644 index b397736..0000000 --- a/src/ThunderDesign.Net-PCL.Threading/Attributes/INotifyPropertyChangedAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ThunderDesign.Net_PCL.Threading.Attributes -{ - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class INotifyPropertyChangedAttribute : Attribute - { - } -} diff --git a/src/ThunderDesign.Net-PCL.Threading/Attributes/PropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading/Attributes/PropertyAttribute.cs deleted file mode 100644 index 1890adc..0000000 --- a/src/ThunderDesign.Net-PCL.Threading/Attributes/PropertyAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ThunderDesign.Net_PCL.Threading.Attributes -{ - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - public sealed class PropertyAttribute : Attribute - { - public bool ReadOnly { get; } - public bool ThreadSafe { get; } - - public PropertyAttribute(bool readOnly = false, bool threadSafe = true) - { - ReadOnly = readOnly; - ThreadSafe = threadSafe; - } - } -} diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/HashSetThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/HashSetThreadSafe.cs index b51a281..b3fde6e 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/HashSetThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/HashSetThreadSafe.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Threading; -using ThunderDesign.Net_PCL.Threading.Interfaces; +using ThunderDesign.Net.Threading.Interfaces; -namespace ThunderDesign.Net_PCL.Threading.Collections +namespace ThunderDesign.Net.Threading.Collections { #if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER public class HashSetThreadSafe : HashSet, IHashSetThreadSafe diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/LinkedListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/LinkedListThreadSafe.cs index cd560a6..3966780 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/LinkedListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/LinkedListThreadSafe.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading; -using ThunderDesign.Net_PCL.Threading.Interfaces; +using ThunderDesign.Net.Threading.Interfaces; -namespace ThunderDesign.Net_PCL.Threading.Collections +namespace ThunderDesign.Net.Threading.Collections { #if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER public class LinkedListThreadSafe : LinkedList, ILinkedListThreadSafe diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs index c79c91c..a79f221 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/ListThreadSafe.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; -using ThunderDesign.Net_PCL.Threading.Interfaces; +using ThunderDesign.Net.Threading.Interfaces; -namespace ThunderDesign.Net_PCL.Threading.Collections +namespace ThunderDesign.Net.Threading.Collections { public class ListThreadSafe : List, IListThreadSafe { diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/SortedDictionaryThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/SortedDictionaryThreadSafe.cs index 886a416..40a77e7 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/SortedDictionaryThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/SortedDictionaryThreadSafe.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading; -using ThunderDesign.Net_PCL.Threading.Interfaces; +using ThunderDesign.Net.Threading.Interfaces; -namespace ThunderDesign.Net_PCL.Threading.Collections +namespace ThunderDesign.Net.Threading.Collections { #if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER public class SortedDictionaryThreadSafe : SortedDictionary, ISortedDictionaryThreadSafe diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs index fd743f9..0127f87 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/SortedListThreadSafe.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading; -using ThunderDesign.Net_PCL.Threading.Interfaces; +using ThunderDesign.Net.Threading.Interfaces; -namespace ThunderDesign.Net_PCL.Threading.Collections +namespace ThunderDesign.Net.Threading.Collections { #if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER public class SortedListThreadSafe : SortedList, ISortedListThreadSafe diff --git a/src/ThunderDesign.Net-PCL.Threading/Collections/StackThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Collections/StackThreadSafe.cs index 3ea5d7d..2c77319 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Collections/StackThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Collections/StackThreadSafe.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading; -using ThunderDesign.Net_PCL.Threading.Interfaces; +using ThunderDesign.Net.Threading.Interfaces; -namespace ThunderDesign.Net_PCL.Threading.Collections +namespace ThunderDesign.Net.Threading.Collections { #if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER public class StackThreadSafe : Stack, IStackThreadSafe diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IHashSetThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IHashSetThreadSafe.cs index c491fc0..b9fb627 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IHashSetThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IHashSetThreadSafe.cs @@ -4,7 +4,7 @@ using System.Runtime.Serialization; #endif -namespace ThunderDesign.Net_PCL.Threading.Interfaces +namespace ThunderDesign.Net.Threading.Interfaces { #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER public interface IHashSetThreadSafe : ICollection, ISet, IReadOnlyCollection, ISerializable, IDeserializationCallback diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ILinkedListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ILinkedListThreadSafe.cs index 8580cce..8d3edb4 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ILinkedListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ILinkedListThreadSafe.cs @@ -4,7 +4,7 @@ using System.Runtime.Serialization; #endif -namespace ThunderDesign.Net_PCL.Threading.Interfaces +namespace ThunderDesign.Net.Threading.Interfaces { #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER public interface ILinkedListThreadSafe : ICollection, ICollection, IReadOnlyCollection, ISerializable, IDeserializationCallback diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IListThreadSafe.cs index eb9b1b1..19a9414 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IListThreadSafe.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; -namespace ThunderDesign.Net_PCL.Threading.Interfaces +namespace ThunderDesign.Net.Threading.Interfaces { public interface IListThreadSafe : IList { diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedDictionaryThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedDictionaryThreadSafe.cs index cffa0be..57f1d69 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedDictionaryThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedDictionaryThreadSafe.cs @@ -4,7 +4,7 @@ using System.Runtime.Serialization; #endif -namespace ThunderDesign.Net_PCL.Threading.Interfaces +namespace ThunderDesign.Net.Threading.Interfaces { #if NET8_0_OR_GREATER public interface ISortedDictionaryThreadSafe : IDictionary, IDictionary, IReadOnlyDictionary diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedListThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedListThreadSafe.cs index ace913a..9f2dcd3 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedListThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/ISortedListThreadSafe.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; -namespace ThunderDesign.Net_PCL.Threading.Interfaces +namespace ThunderDesign.Net.Threading.Interfaces { public interface ISortedListThreadSafe : IDictionary { diff --git a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IStackThreadSafe.cs b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IStackThreadSafe.cs index e729a93..6ca528f 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Interfaces/IStackThreadSafe.cs +++ b/src/ThunderDesign.Net-PCL.Threading/Interfaces/IStackThreadSafe.cs @@ -3,7 +3,7 @@ using System.Runtime.Serialization; #endif -namespace ThunderDesign.Net_PCL.Threading.Interfaces +namespace ThunderDesign.Net.Threading.Interfaces { #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER public interface IStackThreadSafe : IEnumerable, System.Collections.ICollection, IReadOnlyCollection diff --git a/src/ThunderDesign.Net-PCL.Threading/ThunderDesign.Net-PCL.Threading.csproj b/src/ThunderDesign.Net-PCL.Threading/ThunderDesign.Net-PCL.Threading.csproj index f3ef104..1c33c8c 100644 --- a/src/ThunderDesign.Net-PCL.Threading/ThunderDesign.Net-PCL.Threading.csproj +++ b/src/ThunderDesign.Net-PCL.Threading/ThunderDesign.Net-PCL.Threading.csproj @@ -2,7 +2,7 @@ netstandard1.0;netstandard1.3;netstandard2.0;net461;net6.0;net8.0 - ThunderDesign.Net_PCL.Threading + ThunderDesign.Net.Threading README.md