From e42d14a09ca9943b4f09898db5bd0d7a659fb143 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Sat, 21 Jun 2025 18:27:33 -0400 Subject: [PATCH 01/33] Added AccessorAccessibility to Attributes --- README.md | 115 +++++++++++++++++- .../{ => Helpers}/PropertyGeneratorHelpers.cs | 12 +- .../UnifiedPropertyGenerator.cs | 60 ++++++--- .../Attributes/BindablePropertyAttribute.cs | 9 +- .../Attributes/PropertyAttribute.cs | 28 +++++ .../Enums/AccessorAccessibility.cs | 12 ++ .../Attributes/AlsoNotify.cs | 15 --- .../INotifyPropertyChangedAttribute.cs | 11 -- .../Attributes/PropertyAttribute.cs | 19 --- 9 files changed, 208 insertions(+), 73 deletions(-) rename src/ThunderDesign.Net-PCL.SourceGenerators/{ => Helpers}/PropertyGeneratorHelpers.cs (95%) rename src/{ThunderDesign.Net-PCL.Threading => ThunderDesign.Net-PCL.Threading.Shared}/Attributes/BindablePropertyAttribute.cs (64%) create mode 100644 src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs create mode 100644 src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs delete mode 100644 src/ThunderDesign.Net-PCL.Threading/Attributes/AlsoNotify.cs delete mode 100644 src/ThunderDesign.Net-PCL.Threading/Attributes/INotifyPropertyChangedAttribute.cs delete mode 100644 src/ThunderDesign.Net-PCL.Threading/Attributes/PropertyAttribute.cs diff --git a/README.md b/README.md index 0a2e1ba..0445bf3 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,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; @@ -142,7 +140,7 @@ public partial class Person : IBindableObject, INotifyPropertyChanged public int Age { get { return this.GetProperty(ref _age, _Locker); } - set { this.SetProperty(ref _age, value, _Locker); } + set { this.SetProperty(ref _age, value, _Locker, true); } } } ``` @@ -155,11 +153,116 @@ 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 visibility). + +### Advanced: Customizing Property Accessors + +You can now control the visibility of generated property getters and setters using the `AccessorAccessibility` enum. +This allows you to generate properties with `private`, `protected`, `internal`, or other C# accessibilities for the `get` and `set` accessors. + +#### Example + +```csharp +using ThunderDesign.Net.Threading.Attributes; +using ThunderDesign.Net_PCL.Threading.Shared.Enums; + +public partial class Person +{ + // Public getter, private setter [BindableProperty(getter: AccessorAccessibility.Public, setter: AccessorAccessibility.Private)] private string _name; + // Internal getter, protected setter + [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 + { + get { return this.GetProperty(ref _age, _Locker); } + protected set { this.SetProperty(ref _age, value, _Locker); } + } +} +``` + +> The default for both getter and setter is `public` if not specified. + +**Available options for `AccessorAccessibility`:** +- `Public` +- `Private` +- `Protected` +- `Internal` +- `ProtectedInternal` +- `PrivateProtected` + +### Advanced: Notify Other Properties + +You can now 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 + { + this.SetProperty(ref _lastName, value, _Locker, true); + OnPropertyChanged(nameof(DisplayName)); + } + } + + public string DisplayName => $"{FirstName} {LastName}"; + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} +``` +> This feature is particularly useful for computed properties like `DisplayName` that depend on other properties. ---- 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..72b424f 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_PCL.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/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index bcb9796..bae0813 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Text; +using ThunderDesign.Net_PCL.SourceGenerators.Helpers; namespace ThunderDesign.Net_PCL.SourceGenerators { @@ -200,10 +201,25 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem }"); } + // Helper to convert AccessorAccessibility to C# keyword + static string ToAccessorString(object accessor) + { + var value = accessor?.ToString() ?? "Public"; + return value switch + { + "Public" => "", + "Private" => "private ", + "Protected" => "protected ", + "Internal" => "internal ", + "ProtectedInternal" => "protected internal ", + "PrivateProtected" => "private protected ", + _ => "" + }; + } + // Generate all bindable properties 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,13 +232,14 @@ 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!; + 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 = null; - if (info.AttributeData.ConstructorArguments.Length > 3) + if (args.Length > 3) { - var arg = info.AttributeData.ConstructorArguments[3]; + var arg = args[3]; if (arg.Kind == TypedConstantKind.Array && arg.Values != null) { alsoNotify = arg.Values @@ -234,6 +251,13 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem if (alsoNotify == null) alsoNotify = new string[0]; + // Default to Public if not present + var getter = args.Length > 4 ? args[4].Value : null; + var setter = args.Length > 5 ? args[5].Value : null; + + var getterStr = ToAccessorString(getter); + var setterStr = ToAccessorString(setter); + var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; if (readOnly) @@ -241,7 +265,7 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem source.AppendLine($@" public {typeName} {propertyName} {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); } else @@ -256,7 +280,7 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem notifyCalls.AppendLine($" this.OnPropertyChanged(\"{prop}\");"); } setAccessor = $@" - set + {setterStr}set {{ if (this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg})) {{ @@ -266,13 +290,13 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem } else { - setAccessor = $"set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }}"; + setAccessor = $"{setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }}"; } source.AppendLine($@" public {typeName} {propertyName} {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} {setAccessor} }}"); } @@ -294,8 +318,14 @@ 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 getter = args.Length > 2 ? args[2].Value : null; + var setter = args.Length > 3 ? args[3].Value : null; + + var getterStr = ToAccessorString(getter); + var setterStr = ToAccessorString(setter); var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) @@ -303,7 +333,7 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem source.AppendLine($@" public {typeName} {propertyName} {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); } else @@ -311,8 +341,8 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem source.AppendLine($@" public {typeName} {propertyName} {{ - get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} + {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} }}"); } } diff --git a/src/ThunderDesign.Net-PCL.Threading/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs similarity index 64% rename from src/ThunderDesign.Net-PCL.Threading/Attributes/BindablePropertyAttribute.cs rename to src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs index de464d1..61db2cb 100644 --- a/src/ThunderDesign.Net-PCL.Threading/Attributes/BindablePropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -1,4 +1,5 @@ using System; +using ThunderDesign.Net.Threading.Enums; namespace ThunderDesign.Net_PCL.Threading.Attributes { @@ -9,17 +10,23 @@ public sealed class BindablePropertyAttribute : Attribute 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) + string[] alsoNotify = null, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public) { ReadOnly = readOnly; ThreadSafe = threadSafe; Notify = notify; AlsoNotify = alsoNotify ?? new string[0]; + Getter = getter; + Setter = setter; } } } \ 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..ef98d19 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ThunderDesign.Net.Threading.Enums; + +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 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; + } + } +} 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..051a7a1 --- /dev/null +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs @@ -0,0 +1,12 @@ +namespace ThunderDesign.Net.Threading.Enums +{ + public enum AccessorAccessibility + { + Public, + Private, + Protected, + Internal, + ProtectedInternal, + PrivateProtected + } +} \ No newline at end of file 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/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; - } - } -} From 1b6679944cefcc7bb5c62276c7fdb516e1930f1f Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:31:02 -0400 Subject: [PATCH 02/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 7cd14ce..ef2585b 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -1,7 +1,7 @@ name: CD on: - # workflow_dispatch: + workflow_dispatch: release: types: [published] @@ -61,16 +61,16 @@ 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 ${{ github.event.release.tag_name }} -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.2 -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}}.${{ github.event.release.tag_name }} - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.2 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.2.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 - - name: Publish NuGet Package - run: nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} + # - name: Publish NuGet Package + # run: nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} From 2ec673f4d9688c3319cb5a0f18c26a5aa232d1dd Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Mon, 23 Jun 2025 22:57:26 -0400 Subject: [PATCH 03/33] Property generated by Source Generator now chooses its Accessibility from the highest access from getter or setter --- README.md | 75 +++++++++++-------- .../Helpers/PropertyGeneratorHelpers.cs | 2 +- ...nderDesign.Net-PCL.SourceGenerators.csproj | 2 +- .../UnifiedPropertyGenerator.cs | 41 +++++++--- .../Attributes/BindablePropertyAttribute.cs | 2 +- .../Attributes/PropertyAttribute.cs | 4 +- ...nderDesign.Net-PCL.Threading.Shared.csproj | 2 +- .../Collections/HashSetThreadSafe.cs | 4 +- .../Collections/LinkedListThreadSafe.cs | 4 +- .../Collections/ListThreadSafe.cs | 4 +- .../Collections/SortedDictionaryThreadSafe.cs | 4 +- .../Collections/SortedListThreadSafe.cs | 4 +- .../Collections/StackThreadSafe.cs | 4 +- .../Interfaces/IHashSetThreadSafe.cs | 2 +- .../Interfaces/ILinkedListThreadSafe.cs | 2 +- .../Interfaces/IListThreadSafe.cs | 2 +- .../Interfaces/ISortedDictionaryThreadSafe.cs | 2 +- .../Interfaces/ISortedListThreadSafe.cs | 2 +- .../Interfaces/IStackThreadSafe.cs | 2 +- .../ThunderDesign.Net-PCL.Threading.csproj | 2 +- 20 files changed, 99 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 0445bf3..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] @@ -128,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 @@ -140,7 +140,7 @@ public partial class Person : IBindableObject, INotifyPropertyChanged public int Age { get { return this.GetProperty(ref _age, _Locker); } - set { this.SetProperty(ref _age, value, _Locker, true); } + set { this.SetProperty(ref _age, value, _Locker); } } } ``` @@ -148,50 +148,62 @@ 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, or control accessor visibility). +> 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 Property Accessors +### Advanced: Customizing Getter and Setter Accessors -You can now control the visibility of generated property getters and setters using the `AccessorAccessibility` enum. -This allows you to generate properties with `private`, `protected`, `internal`, or other C# accessibilities for the `get` and `set` 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_PCL.Threading.Shared.Enums; +using ThunderDesign.Net.Threading.Attributes; +using ThunderDesign.Net.Threading.Enums; public partial class Person { - // Public getter, private setter [BindableProperty(getter: AccessorAccessibility.Public, setter: AccessorAccessibility.Private)] private string _name; - // Internal getter, protected setter + // 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); } } + public string Name + { + get { return this.GetProperty(ref _name, _Locker); } + private set { this.SetProperty(ref _name, value, _Locker, true); } + } + internal int Age { - get { return this.GetProperty(ref _age, _Locker); } + internal get { return this.GetProperty(ref _age, _Locker); } protected set { this.SetProperty(ref _age, value, _Locker); } } } + ``` -> The default for both getter and setter is `public` if not specified. +> 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` @@ -201,12 +213,13 @@ public partial class Person - `ProtectedInternal` - `PrivateProtected` +--- + ### Advanced: Notify Other Properties -You can now notify other properties when a specific property changes by using the `alsoNotify` parameter in the `[BindableProperty]` attribute. +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; @@ -228,7 +241,6 @@ public partial class Person public partial class Person : IBindableObject, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; - protected readonly object _Locker = new object(); public string FirstName @@ -248,23 +260,20 @@ public partial class Person : IBindableObject, INotifyPropertyChanged get { return this.GetProperty(ref _lastName, _Locker); } set { - this.SetProperty(ref _lastName, value, _Locker, true); - OnPropertyChanged(nameof(DisplayName)); + if (this.SetProperty(ref _lastName, value, _Locker, true)) + { + this.OnPropertyChanged(nameof(DisplayName)); + } } } public string DisplayName => $"{FirstName} {LastName}"; - - protected void OnPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } } ``` > This feature is particularly useful for computed properties like `DisplayName` that depend on other properties. ----- +--- ## Installation @@ -302,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/Helpers/PropertyGeneratorHelpers.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/Helpers/PropertyGeneratorHelpers.cs index 72b424f..a71f7da 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/Helpers/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.Helpers +namespace ThunderDesign.Net.SourceGenerators.Helpers { internal static class PropertyGeneratorHelpers { 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 bae0813..2abe1cb 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -6,9 +6,9 @@ using System.Diagnostics; using System.Linq; using System.Text; -using ThunderDesign.Net_PCL.SourceGenerators.Helpers; +using ThunderDesign.Net.SourceGenerators.Helpers; -namespace ThunderDesign.Net_PCL.SourceGenerators +namespace ThunderDesign.Net.SourceGenerators { [Generator] public class UnifiedPropertyGenerator : IIncrementalGenerator @@ -201,7 +201,7 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem }"); } - // Helper to convert AccessorAccessibility to C# keyword + // Helper to convert AccessorAccessibility to C# keyword (empty string means public) static string ToAccessorString(object accessor) { var value = accessor?.ToString() ?? "Public"; @@ -217,6 +217,29 @@ static string ToAccessorString(object accessor) }; } + // Helper to rank accessibilities for comparison + static int GetAccessibilityRank(string access) + { + return access switch + { + "Public" => 6, + "ProtectedInternal" => 5, + "Internal" => 4, + "Protected" => 3, + "PrivateProtected" => 2, + "Private" => 1, + _ => 0 + }; + } + + // Helper to get the widest (most accessible) accessibility + static string GetWidestAccessibility(object getter, object setter) + { + string getterStr = getter?.ToString() ?? "Public"; + string setterStr = setter?.ToString() ?? "Public"; + return GetAccessibilityRank(getterStr) >= GetAccessibilityRank(setterStr) ? getterStr : setterStr; + } + // Generate all bindable properties foreach (var info in bindableFields) { @@ -251,19 +274,19 @@ static string ToAccessorString(object accessor) if (alsoNotify == null) alsoNotify = new string[0]; - // Default to Public if not present var getter = args.Length > 4 ? args[4].Value : null; var setter = args.Length > 5 ? args[5].Value : null; var getterStr = ToAccessorString(getter); var setterStr = ToAccessorString(setter); + var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter, setter)); var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; if (readOnly) { source.AppendLine($@" - public {typeName} {propertyName} + {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); @@ -294,7 +317,7 @@ static string ToAccessorString(object accessor) } source.AppendLine($@" - public {typeName} {propertyName} + {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} {setAccessor} @@ -305,7 +328,6 @@ static string ToAccessorString(object accessor) // Generate all regular properties 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) || @@ -326,12 +348,13 @@ static string ToAccessorString(object accessor) var getterStr = ToAccessorString(getter); var setterStr = ToAccessorString(setter); + var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter, setter)); var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) { source.AppendLine($@" - public {typeName} {propertyName} + {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); @@ -339,7 +362,7 @@ static string ToAccessorString(object accessor) else { source.AppendLine($@" - public {typeName} {propertyName} + {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} {setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs index 61db2cb..c08a617 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -1,7 +1,7 @@ using System; using ThunderDesign.Net.Threading.Enums; -namespace ThunderDesign.Net_PCL.Threading.Attributes +namespace ThunderDesign.Net.Threading.Attributes { [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] public sealed class BindablePropertyAttribute : Attribute diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs index ef98d19..1f23c69 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; -using System.Text; using ThunderDesign.Net.Threading.Enums; -namespace ThunderDesign.Net_PCL.Threading.Attributes +namespace ThunderDesign.Net.Threading.Attributes { [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public sealed class PropertyAttribute : Attribute 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/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 From df51030f88df667deb97c84f0941c87fe712f21b Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:02:42 -0400 Subject: [PATCH 04/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index ef2585b..7e8946b 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 2.0.2 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.3 -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}}.2.0.2 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.2.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.3 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.3.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 From 0f68b380ea21b4704225d3629ea43026d20714fb Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Mon, 23 Jun 2025 23:13:11 -0400 Subject: [PATCH 05/33] Fixed issue with Generated property defaulting to Public if getter nor setter was passed in --- .../UnifiedPropertyGenerator.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index 2abe1cb..ffec57d 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -277,9 +277,10 @@ static string GetWidestAccessibility(object getter, object setter) var getter = args.Length > 4 ? args[4].Value : null; var setter = args.Length > 5 ? args[5].Value : null; - var getterStr = ToAccessorString(getter); - var setterStr = ToAccessorString(setter); - var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter, setter)); + // If getter/setter are not specified, default to "Public" + var getterStr = ToAccessorString(getter ?? "Public"); + var setterStr = ToAccessorString(setter ?? "Public"); + var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter ?? "Public", setter ?? "Public")); var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; @@ -346,9 +347,10 @@ static string GetWidestAccessibility(object getter, object setter) var getter = args.Length > 2 ? args[2].Value : null; var setter = args.Length > 3 ? args[3].Value : null; - var getterStr = ToAccessorString(getter); - var setterStr = ToAccessorString(setter); - var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter, setter)); + // If getter/setter are not specified, default to "Public" + var getterStr = ToAccessorString(getter ?? "Public"); + var setterStr = ToAccessorString(setter ?? "Public"); + var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter ?? "Public", setter ?? "Public")); var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) From 832a8db75a6593821b874e343f1be86fbff0db25 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:14:10 -0400 Subject: [PATCH 06/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 7e8946b..4571d64 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 2.0.3 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.4 -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}}.2.0.3 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.3.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.4 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.4.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 From 3e5623f43f379a34b9292ea63de9b16ab08f1acf Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Mon, 23 Jun 2025 23:29:43 -0400 Subject: [PATCH 07/33] Fixxing issue with missing AccessorAccess before Source Generator Property --- .../UnifiedPropertyGenerator.cs | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index ffec57d..87546d0 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -201,22 +201,29 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem }"); } - // Helper to convert AccessorAccessibility to C# keyword (empty string means public) - static string ToAccessorString(object accessor) + // Helper to convert AccessorAccessibility to C# keyword for property (never empty) + static string ToPropertyAccessibilityString(string access) { - var value = accessor?.ToString() ?? "Public"; - return value switch + return access switch { - "Public" => "", + "Public" => "public ", "Private" => "private ", "Protected" => "protected ", "Internal" => "internal ", "ProtectedInternal" => "protected internal ", "PrivateProtected" => "private protected ", - _ => "" + _ => "public " }; } + // Helper to convert AccessorAccessibility to C# keyword for accessor (empty if matches property) + static string ToAccessorAccessibilityString(string accessor, string property) + { + if (string.Equals(accessor, property, System.StringComparison.OrdinalIgnoreCase) || accessor == null) + return ""; + return ToPropertyAccessibilityString(accessor); + } + // Helper to rank accessibilities for comparison static int GetAccessibilityRank(string access) { @@ -277,10 +284,13 @@ static string GetWidestAccessibility(object getter, object setter) var getter = args.Length > 4 ? args[4].Value : null; var setter = args.Length > 5 ? args[5].Value : null; - // If getter/setter are not specified, default to "Public" - var getterStr = ToAccessorString(getter ?? "Public"); - var setterStr = ToAccessorString(setter ?? "Public"); - var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter ?? "Public", setter ?? "Public")); + // Get string values for getter/setter, defaulting to "Public" + string getterValue = (getter ?? "Public").ToString(); + string setterValue = (setter ?? "Public").ToString(); + string propertyAccess = GetWidestAccessibility(getterValue, setterValue); + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccess); + string getterStr = ToAccessorAccessibilityString(getterValue, propertyAccess); + string setterStr = ToAccessorAccessibilityString(setterValue, propertyAccess); var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; @@ -347,10 +357,13 @@ static string GetWidestAccessibility(object getter, object setter) var getter = args.Length > 2 ? args[2].Value : null; var setter = args.Length > 3 ? args[3].Value : null; - // If getter/setter are not specified, default to "Public" - var getterStr = ToAccessorString(getter ?? "Public"); - var setterStr = ToAccessorString(setter ?? "Public"); - var propertyAccessibilityStr = ToAccessorString(GetWidestAccessibility(getter ?? "Public", setter ?? "Public")); + // Get string values for getter/setter, defaulting to "Public" + string getterValue = (getter ?? "Public").ToString(); + string setterValue = (setter ?? "Public").ToString(); + string propertyAccess = GetWidestAccessibility(getterValue, setterValue); + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccess); + string getterStr = ToAccessorAccessibilityString(getterValue, propertyAccess); + string setterStr = ToAccessorAccessibilityString(setterValue, propertyAccess); var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) From 691c50b2a2ede5fa793a5a231f3b1a72c892f5d5 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:31:10 -0400 Subject: [PATCH 08/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 4571d64..acdf989 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 2.0.4 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.5 -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}}.2.0.4 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.4.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.5 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.5.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 From 1f5e91232cf821140a16bff4b88d3439f708bbcb Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Mon, 23 Jun 2025 23:49:20 -0400 Subject: [PATCH 09/33] Issues with getter and setter values --- .../UnifiedPropertyGenerator.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index 87546d0..c6f7eb5 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -216,8 +216,8 @@ static string ToPropertyAccessibilityString(string access) }; } - // Helper to convert AccessorAccessibility to C# keyword for accessor (empty if matches property) - static string ToAccessorAccessibilityString(string accessor, string property) + // Helper to get the accessor modifier (empty if matches property) + static string ToAccessorModifier(string accessor, string property) { if (string.Equals(accessor, property, System.StringComparison.OrdinalIgnoreCase) || accessor == null) return ""; @@ -289,8 +289,8 @@ static string GetWidestAccessibility(object getter, object setter) string setterValue = (setter ?? "Public").ToString(); string propertyAccess = GetWidestAccessibility(getterValue, setterValue); string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccess); - string getterStr = ToAccessorAccessibilityString(getterValue, propertyAccess); - string setterStr = ToAccessorAccessibilityString(setterValue, propertyAccess); + string getterStr = ToAccessorModifier(getterValue, propertyAccess); + string setterStr = ToAccessorModifier(setterValue, propertyAccess); var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; @@ -362,8 +362,8 @@ static string GetWidestAccessibility(object getter, object setter) string setterValue = (setter ?? "Public").ToString(); string propertyAccess = GetWidestAccessibility(getterValue, setterValue); string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccess); - string getterStr = ToAccessorAccessibilityString(getterValue, propertyAccess); - string setterStr = ToAccessorAccessibilityString(setterValue, propertyAccess); + string getterStr = ToAccessorModifier(getterValue, propertyAccess); + string setterStr = ToAccessorModifier(setterValue, propertyAccess); var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) From 5fc8c3ee8134390894a83e2af57359b234bdb7c8 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:50:46 -0400 Subject: [PATCH 10/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index acdf989..90eb68a 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 2.0.5 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.6 -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}}.2.0.5 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.5.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.6 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.6.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 From 0a5e5637ab1a092da8ff6979712c9d37b0a55278 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 00:43:05 -0400 Subject: [PATCH 11/33] Fixing issues with getters and setters --- .../UnifiedPropertyGenerator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index c6f7eb5..3ca0ff9 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -217,9 +217,10 @@ static string ToPropertyAccessibilityString(string access) } // Helper to get the accessor modifier (empty if matches property) - static string ToAccessorModifier(string accessor, string property) + static string ToAccessorModifier(string accessor, string propertyAccess) { - if (string.Equals(accessor, property, System.StringComparison.OrdinalIgnoreCase) || accessor == null) + // Compare raw values, not formatted strings + if (string.Equals(accessor, propertyAccess, System.StringComparison.OrdinalIgnoreCase) || accessor == null) return ""; return ToPropertyAccessibilityString(accessor); } @@ -240,11 +241,9 @@ static int GetAccessibilityRank(string access) } // Helper to get the widest (most accessible) accessibility - static string GetWidestAccessibility(object getter, object setter) + static string GetWidestAccessibility(string getter, string setter) { - string getterStr = getter?.ToString() ?? "Public"; - string setterStr = setter?.ToString() ?? "Public"; - return GetAccessibilityRank(getterStr) >= GetAccessibilityRank(setterStr) ? getterStr : setterStr; + return GetAccessibilityRank(getter) >= GetAccessibilityRank(setter) ? getter : setter; } // Generate all bindable properties From d83b3b7ca5c0eec93af37356a9587b2988baf92d Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 00:44:26 -0400 Subject: [PATCH 12/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 90eb68a..f46c73c 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 2.0.6 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.7 -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}}.2.0.6 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.6.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.7 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.7.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 From db416dd4d25d06c7712bf43c947c0ea809f19eb6 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 00:53:36 -0400 Subject: [PATCH 13/33] Still testing getter and setter --- .../UnifiedPropertyGenerator.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index 3ca0ff9..a51bb18 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -217,10 +217,9 @@ static string ToPropertyAccessibilityString(string access) } // Helper to get the accessor modifier (empty if matches property) - static string ToAccessorModifier(string accessor, string propertyAccess) + static string ToAccessorModifier(string accessor, string propertyRaw) { - // Compare raw values, not formatted strings - if (string.Equals(accessor, propertyAccess, System.StringComparison.OrdinalIgnoreCase) || accessor == null) + if (string.Equals(accessor, propertyRaw, System.StringComparison.OrdinalIgnoreCase) || accessor == null) return ""; return ToPropertyAccessibilityString(accessor); } @@ -286,10 +285,10 @@ static string GetWidestAccessibility(string getter, string setter) // Get string values for getter/setter, defaulting to "Public" string getterValue = (getter ?? "Public").ToString(); string setterValue = (setter ?? "Public").ToString(); - string propertyAccess = GetWidestAccessibility(getterValue, setterValue); - string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccess); - string getterStr = ToAccessorModifier(getterValue, propertyAccess); - string setterStr = ToAccessorModifier(setterValue, propertyAccess); + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g., "Public" + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g., "public " + string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // e.g., "" if getter is "Public" + string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // e.g., "private " if setter is "Private" var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; @@ -359,10 +358,10 @@ static string GetWidestAccessibility(string getter, string setter) // Get string values for getter/setter, defaulting to "Public" string getterValue = (getter ?? "Public").ToString(); string setterValue = (setter ?? "Public").ToString(); - string propertyAccess = GetWidestAccessibility(getterValue, setterValue); - string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccess); - string getterStr = ToAccessorModifier(getterValue, propertyAccess); - string setterStr = ToAccessorModifier(setterValue, propertyAccess); + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g., "Public" + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g., "public " + string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // e.g., "" if getter is "Public" + string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // e.g., "private " if setter is "Private" var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) From ff7412866961dbb319ff5581f869541222a0f9e0 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 00:54:31 -0400 Subject: [PATCH 14/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index f46c73c..ef851ca 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 2.0.7 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.8 -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}}.2.0.7 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.7.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.8 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.8.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 From 904bddc92dc62bda980df91dac2deebea8c60f27 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 01:04:41 -0400 Subject: [PATCH 15/33] Still trying to fix getter and setter --- .../UnifiedPropertyGenerator.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index a51bb18..47b12ec 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -201,7 +201,7 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem }"); } - // Helper to convert AccessorAccessibility to C# keyword for property (never empty) + // Helper: C# keyword for property (never empty) static string ToPropertyAccessibilityString(string access) { return access switch @@ -216,15 +216,16 @@ static string ToPropertyAccessibilityString(string access) }; } - // Helper to get the accessor modifier (empty if matches property) - static string ToAccessorModifier(string accessor, string propertyRaw) + // Helper: Only emit accessor modifier if it differs from property (compare raw, not formatted) + static string ToAccessorModifier(string accessor, string propertyAccess) { - if (string.Equals(accessor, propertyRaw, System.StringComparison.OrdinalIgnoreCase) || accessor == null) + if (string.IsNullOrEmpty(accessor)) accessor = "Public"; + if (string.Equals(accessor, propertyAccess, System.StringComparison.OrdinalIgnoreCase)) return ""; return ToPropertyAccessibilityString(accessor); } - // Helper to rank accessibilities for comparison + // Helper: Accessibility rank static int GetAccessibilityRank(string access) { return access switch @@ -239,7 +240,7 @@ static int GetAccessibilityRank(string access) }; } - // Helper to get the widest (most accessible) accessibility + // Helper: Widest accessibility (raw, e.g. "Public") static string GetWidestAccessibility(string getter, string setter) { return GetAccessibilityRank(getter) >= GetAccessibilityRank(setter) ? getter : setter; @@ -282,13 +283,12 @@ static string GetWidestAccessibility(string getter, string setter) var getter = args.Length > 4 ? args[4].Value : null; var setter = args.Length > 5 ? args[5].Value : null; - // Get string values for getter/setter, defaulting to "Public" string getterValue = (getter ?? "Public").ToString(); string setterValue = (setter ?? "Public").ToString(); - string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g., "Public" - string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g., "public " - string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // e.g., "" if getter is "Public" - string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // e.g., "private " if setter is "Private" + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g. "Public" + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g. "public " + string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // "" if getter is "Public" + string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // "private " if setter is "Private" var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; @@ -355,13 +355,12 @@ static string GetWidestAccessibility(string getter, string setter) var getter = args.Length > 2 ? args[2].Value : null; var setter = args.Length > 3 ? args[3].Value : null; - // Get string values for getter/setter, defaulting to "Public" string getterValue = (getter ?? "Public").ToString(); string setterValue = (setter ?? "Public").ToString(); - string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g., "Public" - string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g., "public " - string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // e.g., "" if getter is "Public" - string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // e.g., "private " if setter is "Private" + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g. "Public" + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g. "public " + string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // "" if getter is "Public" + string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // "private " if setter is "Private" var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) From b3bb21a7d5d1079b257ccc1d508b5d8d32cbde6a Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 01:05:40 -0400 Subject: [PATCH 16/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index ef851ca..bc6f3e9 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 2.0.8 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.9 -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}}.2.0.8 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.8.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.9 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.9.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 From 761d849619a5430bc7896f6d59e7aa7bb2f5ec52 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 01:27:28 -0400 Subject: [PATCH 17/33] Still fixing getter and setter --- .../UnifiedPropertyGenerator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index 47b12ec..2670161 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -216,7 +216,7 @@ static string ToPropertyAccessibilityString(string access) }; } - // Helper: Only emit accessor modifier if it differs from property (compare raw, not formatted) + // Helper: Only emit accessor modifier if it differs from property static string ToAccessorModifier(string accessor, string propertyAccess) { if (string.IsNullOrEmpty(accessor)) accessor = "Public"; @@ -322,14 +322,15 @@ static string GetWidestAccessibility(string getter, string setter) } else { - setAccessor = $"{setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }}"; + setAccessor = $@" + {setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }}"; } source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - {setAccessor} +{setAccessor} }}"); } } From 92bbf869da62eb200066171171301af9ab6d27af Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 01:28:19 -0400 Subject: [PATCH 18/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index bc6f3e9..1fc89ec 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 2.0.9 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.10 -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}}.2.0.9 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.9.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.10 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.10.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 From b764a83fe4c2f98c138990fa2526b3a782ab01e2 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 01:41:01 -0400 Subject: [PATCH 19/33] Working on getters and setters --- .../UnifiedPropertyGenerator.cs | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index 2670161..220bb91 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -294,15 +295,22 @@ static string GetWidestAccessibility(string getter, string setter) var notifyArg = notify ? "true" : "false"; if (readOnly) { + string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue.ToString()); + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ - {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); } else { - string setAccessor; + string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue.ToString()); + if (alsoNotify.Length > 0) { var notifyCalls = new StringBuilder(); @@ -311,27 +319,37 @@ static string GetWidestAccessibility(string getter, string setter) if (!string.IsNullOrEmpty(prop)) notifyCalls.AppendLine($" this.OnPropertyChanged(\"{prop}\");"); } - setAccessor = $@" - {setterStr}set + + string setterModifier = setterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue.ToString()); + + 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 = $@" - {setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}, {notifyArg}); }}"; - } + string setterModifier = setterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue.ToString()); - source.AppendLine($@" + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ - {getterStr}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}); }} }}"); + } } } @@ -366,19 +384,31 @@ static string GetWidestAccessibility(string getter, string setter) var lockerArg = threadSafe ? "_Locker" : "null"; if (readOnly) { + string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue.ToString()); + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ - {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); } else { + string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(getterValue.ToString()); + + string setterModifier = setterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue.ToString()); + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ - {getterStr}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - {setterStr}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} + {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} + {setterModifier}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} }}"); } } From 55bff217fac127dd5ac8bd8ea7cb9516c0cf4bc8 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 01:41:53 -0400 Subject: [PATCH 20/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 1fc89ec..21488f1 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 2.0.10 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.11 -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}}.2.0.10 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.10.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.11 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.11.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 From 4b9bfe614568f40cbe7a8f5eb58905465b8dd1ba Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 02:00:27 -0400 Subject: [PATCH 21/33] Testing getters and setters --- .../UnifiedPropertyGenerator.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index 220bb91..efa1736 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -281,11 +281,13 @@ static string GetWidestAccessibility(string getter, string setter) if (alsoNotify == null) alsoNotify = new string[0]; - var getter = args.Length > 4 ? args[4].Value : null; - var setter = args.Length > 5 ? args[5].Value : null; + 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 getterValue = (getter ?? "Public").ToString(); - string setterValue = (setter ?? "Public").ToString(); string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g. "Public" string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g. "public " string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // "" if getter is "Public" @@ -371,11 +373,13 @@ static string GetWidestAccessibility(string getter, string setter) var args = info.AttributeData.ConstructorArguments; var readOnly = args.Length > 0 && (bool)args[0].Value!; var threadSafe = args.Length > 1 && (bool)args[1].Value!; - var getter = args.Length > 2 ? args[2].Value : null; - var setter = args.Length > 3 ? args[3].Value : null; + 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 getterValue = (getter ?? "Public").ToString(); - string setterValue = (setter ?? "Public").ToString(); string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g. "Public" string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g. "public " string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // "" if getter is "Public" @@ -432,6 +436,20 @@ private static bool ImplementsInterface(INamedTypeSymbol type, string interfaceN return type.AllInterfaces.Any(i => i.ToDisplayString() == interfaceName); } + private static string GetAccessibilityName(int value) + { + return value switch + { + 0 => "Public", + 1 => "Private", + 2 => "Protected", + 3 => "Internal", + 4 => "ProtectedInternal", + 5 => "PrivateProtected", + _ => "Public" // Default to Public for safety + }; + } + private struct BindableFieldInfo { public IFieldSymbol FieldSymbol { get; set; } From 304bca4581dd3f8e47f909f1a1f1391e91d44e91 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 02:01:59 -0400 Subject: [PATCH 22/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 21488f1..4e016e4 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 2.0.11 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.12 -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}}.2.0.11 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.11.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.12 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.12.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 From ef8f7b088c860bdefeb617ed7b4cd40774822129 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 02:56:22 -0400 Subject: [PATCH 23/33] optimized the code now that things are working --- .../UnifiedPropertyGenerator.cs | 549 +++++++++++------- 1 file changed, 351 insertions(+), 198 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs index efa1736..bbeadd0 100644 --- a/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs +++ b/src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs @@ -4,9 +4,11 @@ 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.SourceGenerators @@ -14,6 +16,36 @@ 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) @@ -22,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 + ); + } + + 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; + } - context.RegisterSourceOutput(grouped.Combine(compilationProvider), (spc, tuple) => + 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) @@ -80,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 }; @@ -92,7 +131,7 @@ private static BindableFieldInfo GetBindableField(GeneratorSyntaxContext context } } } - return default(BindableFieldInfo); + return default; } private static void GenerateUnifiedPropertyClass( @@ -102,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"); @@ -110,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( @@ -201,53 +308,13 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem this.NotifyPropertyChanged(PropertyChanged, propertyName); }"); } + } - // Helper: C# keyword for property (never empty) - static string ToPropertyAccessibilityString(string access) - { - return access switch - { - "Public" => "public ", - "Private" => "private ", - "Protected" => "protected ", - "Internal" => "internal ", - "ProtectedInternal" => "protected internal ", - "PrivateProtected" => "private protected ", - _ => "public " - }; - } - - // Helper: Only emit accessor modifier if it differs from property - static string ToAccessorModifier(string accessor, string propertyAccess) - { - if (string.IsNullOrEmpty(accessor)) accessor = "Public"; - if (string.Equals(accessor, propertyAccess, System.StringComparison.OrdinalIgnoreCase)) - return ""; - return ToPropertyAccessibilityString(accessor); - } - - // Helper: Accessibility rank - static int GetAccessibilityRank(string access) - { - return access switch - { - "Public" => 6, - "ProtectedInternal" => 5, - "Internal" => 4, - "Protected" => 3, - "PrivateProtected" => 2, - "Private" => 1, - _ => 0 - }; - } - - // Helper: Widest accessibility (raw, e.g. "Public") - static string GetWidestAccessibility(string getter, string setter) - { - return GetAccessibilityRank(getter) >= GetAccessibilityRank(setter) ? getter : setter; - } - - // Generate all bindable properties + private static void GenerateBindableProperties( + StringBuilder source, + List bindableFields, + INamedTypeSymbol classSymbol) + { foreach (var info in bindableFields) { var propertyName = PropertyGeneratorHelpers.ToPropertyName(info.FieldSymbol.Name); @@ -266,20 +333,8 @@ static string GetWidestAccessibility(string getter, string setter) 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 = null; - if (args.Length > 3) - { - var arg = args[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]; + + string[] alsoNotify = GetAlsoNotifyProperties(args); var getterEnum = args.Length > 4 ? args[4].Value : null; var setterEnum = args.Length > 5 ? args[5].Value : null; @@ -288,45 +343,111 @@ static string GetWidestAccessibility(string getter, string setter) string getterValue = getterEnum != null ? GetAccessibilityName((int)getterEnum) : "Public"; string setterValue = setterEnum != null ? GetAccessibilityName((int)setterEnum) : "Public"; - string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g. "Public" - string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g. "public " - string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // "" if getter is "Public" - string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // "private " if setter is "Private" - + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); + var lockerArg = threadSafe ? "_Locker" : "null"; var notifyArg = notify ? "true" : "false"; + if (readOnly) { - string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(getterValue.ToString()); + GenerateReadOnlyBindableProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg); + } + else + { + 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($@" + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} }}"); - } - else - { - string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(getterValue.ToString()); + } - if (alsoNotify.Length > 0) - { - var notifyCalls = new StringBuilder(); - foreach (var prop in alsoNotify) - { - if (!string.IsNullOrEmpty(prop)) - notifyCalls.AppendLine($" this.OnPropertyChanged(\"{prop}\");"); - } + 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.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(setterValue.ToString()); + string setterModifier = setterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue); - source.AppendLine($@" + 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}); }} @@ -338,24 +459,23 @@ static string GetWidestAccessibility(string getter, string setter) }} }} }}"); - } - else - { - string setterModifier = setterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(setterValue.ToString()); - - source.AppendLine($@" + } + else + { + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ {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) { var propertyName = PropertyGeneratorHelpers.ToPropertyName(info.FieldSymbol.Name); @@ -380,55 +500,85 @@ static string GetWidestAccessibility(string getter, string setter) string getterValue = getterEnum != null ? GetAccessibilityName((int)getterEnum) : "Public"; string setterValue = setterEnum != null ? GetAccessibilityName((int)setterEnum) : "Public"; - string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); // e.g. "Public" - string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); // e.g. "public " - string getterStr = ToAccessorModifier(getterValue, propertyAccessRaw); // "" if getter is "Public" - string setterStr = ToAccessorModifier(setterValue, propertyAccessRaw); // "private " if setter is "Private" - + string propertyAccessRaw = GetWidestAccessibility(getterValue, setterValue); + string propertyAccessibilityStr = ToPropertyAccessibilityString(propertyAccessRaw); + var lockerArg = threadSafe ? "_Locker" : "null"; + if (readOnly) { - string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(getterValue.ToString()); - - source.AppendLine($@" - {propertyAccessibilityStr}{typeName} {propertyName} - {{ - {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - }}"); + GenerateReadOnlyProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg); } else { - string getterModifier = getterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(getterValue.ToString()); + GenerateReadWriteProperty( + source, + propertyAccessibilityStr, + typeName, + propertyName, + getterValue, + propertyAccessRaw, + fieldName, + lockerArg, + setterValue); + } + } + } - string setterModifier = setterValue.ToString().Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) - ? "" - : ToPropertyAccessibilityString(setterValue.ToString()); + 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); - source.AppendLine($@" + source.AppendLine($@" {propertyAccessibilityStr}{typeName} {propertyName} {{ {getterModifier}get {{ return this.GetProperty(ref {fieldName}, {lockerArg}); }} - {setterModifier}set {{ this.SetProperty(ref {fieldName}, value, {lockerArg}); }} }}"); - } - } + } - source.AppendLine("}"); + 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); - if (!string.IsNullOrEmpty(ns)) - source.AppendLine("}"); + string setterModifier = setterValue.Equals(propertyAccessRaw, StringComparison.OrdinalIgnoreCase) + ? "" + : ToPropertyAccessibilityString(setterValue); - // Ensure unique hintName by including the full metadata name - var safeClassName = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - .Replace(".", "_") - .Replace("global::", ""); - var hintName = $"{safeClassName}_AllProperties.g.cs"; - - context.AddSource(hintName, SourceText.From(source.ToString(), Encoding.UTF8)); + 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) @@ -438,16 +588,19 @@ private static bool ImplementsInterface(INamedTypeSymbol type, string interfaceN private static string GetAccessibilityName(int value) { - return value switch - { - 0 => "Public", - 1 => "Private", - 2 => "Protected", - 3 => "Internal", - 4 => "ProtectedInternal", - 5 => "PrivateProtected", - _ => "Public" // Default to Public for safety - }; + 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 From 5d0b36fd82c574cd086217a7a946e58e27941ca5 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 02:57:14 -0400 Subject: [PATCH 24/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 4e016e4..23750ad 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 2.0.12 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.13 -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}}.2.0.12 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.12.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.13 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.13.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 From fd2c25128e8f8e4092373ad4d8e4ac0d278abf98 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 22:00:49 -0400 Subject: [PATCH 25/33] Created multiple constructors for BindablePropertyAttribute --- .../Attributes/BindablePropertyAttribute.cs | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs index c08a617..f918d53 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using ThunderDesign.Net.Threading.Enums; namespace ThunderDesign.Net.Threading.Attributes @@ -6,12 +8,12 @@ namespace ThunderDesign.Net.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 AccessorAccessibility Getter { get; } - public AccessorAccessibility Setter { get; } + public bool ReadOnly { get; private set; } + public bool ThreadSafe { get; private set; } + public bool Notify { get; private set; } + public string[] AlsoNotify { get; private set; } + public AccessorAccessibility Getter { get; private set; } + public AccessorAccessibility Setter { get; private set; } public BindablePropertyAttribute( bool readOnly = false, @@ -20,6 +22,39 @@ public BindablePropertyAttribute( string[] alsoNotify = null, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) + { + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify ?? new string[0], getter, setter); + } + + public BindablePropertyAttribute( + bool readOnly, + bool threadSafe, + bool notify, + IEnumerable alsoNotify, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public) + { + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify?.ToArray() ?? new string[0], getter, setter); + } + + public BindablePropertyAttribute( + bool readOnly, + bool threadSafe, + bool notify, + string alsoNotify, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public) + { + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, string.IsNullOrEmpty(alsoNotify) ? new string[0] : new string[] { alsoNotify }, getter, setter); + } + + public void ApplyBindablePropertyAttribute( + bool readOnly, + bool threadSafe, + bool notify, + string[] alsoNotify, + AccessorAccessibility getter, + AccessorAccessibility setter) { ReadOnly = readOnly; ThreadSafe = threadSafe; From 37bab75ee83d054ccc8351bd0c471cc8547f71b2 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:02:11 -0400 Subject: [PATCH 26/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 23750ad..126ec30 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 2.0.13 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.14 -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}}.2.0.13 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.13.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.14 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.14.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 From d21d6adcadb1cce9fb5dfd66e38e53e63c784204 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 22:21:28 -0400 Subject: [PATCH 27/33] Updated BindablePropertyAttributes --- .../Attributes/BindablePropertyAttribute.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs index f918d53..b024711 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -19,42 +19,52 @@ public BindablePropertyAttribute( bool readOnly = false, bool threadSafe = true, bool notify = true, - string[] alsoNotify = null, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify ?? new string[0], getter, setter); + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, new string[0], getter, setter); } public BindablePropertyAttribute( - bool readOnly, - bool threadSafe, - bool notify, - IEnumerable alsoNotify, + bool readOnly = false, + bool threadSafe = true, + bool notify = true, + string alsoNotify = null, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify?.ToArray() ?? new string[0], getter, setter); + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, string.IsNullOrEmpty(alsoNotify) ? new string[0] : new string[] { alsoNotify }, getter, setter); } - + public BindablePropertyAttribute( - bool readOnly, - bool threadSafe, - bool notify, - string alsoNotify, + string[] alsoNotify = null, + bool readOnly = false, + bool threadSafe = true, + bool notify = true, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, string.IsNullOrEmpty(alsoNotify) ? new string[0] : new string[] { alsoNotify }, getter, setter); + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify ?? new string[0], getter, setter); } + public BindablePropertyAttribute( + bool readOnly = false, + bool threadSafe = true, + IEnumerable alsoNotify = null, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public, + bool notify = true) + { + ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify?.ToArray() ?? new string[0], getter, setter); + } + public void ApplyBindablePropertyAttribute( - bool readOnly, - bool threadSafe, - bool notify, - string[] alsoNotify, - AccessorAccessibility getter, - AccessorAccessibility setter) + bool readOnly = false, + bool threadSafe = true, + bool notify = true, + string[] alsoNotify = null, + AccessorAccessibility getter = AccessorAccessibility.Public, + AccessorAccessibility setter = AccessorAccessibility.Public) { ReadOnly = readOnly; ThreadSafe = threadSafe; From ec60ccaf348a6c6b99b786fa40a4f2823e958faf Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:22:14 -0400 Subject: [PATCH 28/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 126ec30..7ef5f66 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 2.0.14 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.15 -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}}.2.0.14 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.14.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.15 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.15.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 From 2d4832bb6b3185355447988d56b296be88ed053a Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 23:01:16 -0400 Subject: [PATCH 29/33] BindablePropertyAttribute, PropertyAttribute and Accessorccessibility now only compile if .Net Standard 2.0 or higher and .Net 6.0 or higher --- .../Attributes/BindablePropertyAttribute.cs | 32 ++++++++++--------- .../Attributes/PropertyAttribute.cs | 2 ++ .../Enums/AccessorAccessibility.cs | 2 ++ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs index b024711..f21c8a8 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -5,6 +5,7 @@ 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 { @@ -22,56 +23,57 @@ public BindablePropertyAttribute( AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, new string[0], getter, setter); + ApplyBindablePropertyAttribute(Array.Empty(), readOnly, threadSafe, notify, getter, setter); } public BindablePropertyAttribute( + string alsoNotify, bool readOnly = false, bool threadSafe = true, bool notify = true, - string alsoNotify = null, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, string.IsNullOrEmpty(alsoNotify) ? new string[0] : new string[] { alsoNotify }, getter, setter); + ApplyBindablePropertyAttribute(string.IsNullOrEmpty(alsoNotify) ? Array.Empty() : new string[1] { alsoNotify }, readOnly, threadSafe, notify, getter, setter); } public BindablePropertyAttribute( - string[] alsoNotify = null, + string[] alsoNotify, bool readOnly = false, bool threadSafe = true, bool notify = true, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify ?? new string[0], getter, setter); + ApplyBindablePropertyAttribute(alsoNotify ?? Array.Empty(), readOnly, threadSafe, notify, getter, setter); } public BindablePropertyAttribute( + IEnumerable alsoNotify, bool readOnly = false, bool threadSafe = true, - IEnumerable alsoNotify = null, AccessorAccessibility getter = AccessorAccessibility.Public, AccessorAccessibility setter = AccessorAccessibility.Public, bool notify = true) { - ApplyBindablePropertyAttribute(readOnly, threadSafe, notify, alsoNotify?.ToArray() ?? new string[0], getter, setter); + ApplyBindablePropertyAttribute(alsoNotify?.ToArray() ?? Array.Empty(), readOnly, threadSafe, notify, getter, setter); } - public void ApplyBindablePropertyAttribute( - bool readOnly = false, - bool threadSafe = true, - bool notify = true, - string[] alsoNotify = null, - AccessorAccessibility getter = AccessorAccessibility.Public, - AccessorAccessibility setter = AccessorAccessibility.Public) + private void ApplyBindablePropertyAttribute( + string[] alsoNotify, + bool readOnly, + bool threadSafe, + bool notify, + AccessorAccessibility getter, + AccessorAccessibility setter) { ReadOnly = readOnly; ThreadSafe = threadSafe; Notify = notify; - AlsoNotify = alsoNotify ?? new string[0]; + AlsoNotify = alsoNotify; 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 index 1f23c69..e544d01 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs @@ -3,6 +3,7 @@ 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 { @@ -23,4 +24,5 @@ public PropertyAttribute( 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 index 051a7a1..924cee4 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Enums/AccessorAccessibility.cs @@ -1,5 +1,6 @@ namespace ThunderDesign.Net.Threading.Enums { +#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER public enum AccessorAccessibility { Public, @@ -9,4 +10,5 @@ public enum AccessorAccessibility ProtectedInternal, PrivateProtected } +#endif } \ No newline at end of file From 4db9f85888d6f0a7d837ee25fa9a199d35658028 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:02:05 -0400 Subject: [PATCH 30/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 7ef5f66..913de57 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 2.0.15 -OutputDirectory ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + run: nuget pack ThunderDesign.Net-PCL.nuspec -Version 2.0.16 -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}}.2.0.15 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.15.nupkg + name: Package_${{ env.FILE_NAME}}.2.0.16 + path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.16.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 From 783e5523068e2b95b0344bbc46493a528f841d90 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain Date: Tue, 24 Jun 2025 23:32:32 -0400 Subject: [PATCH 31/33] Reverted new constructors for BindablePropertyAttributes. Was causing issues in source generator --- .../Attributes/BindablePropertyAttribute.cs | 59 +++---------------- 1 file changed, 8 insertions(+), 51 deletions(-) diff --git a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs index f21c8a8..133f952 100644 --- a/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs +++ b/src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs @@ -9,68 +9,25 @@ namespace ThunderDesign.Net.Threading.Attributes [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] public sealed class BindablePropertyAttribute : Attribute { - public bool ReadOnly { get; private set; } - public bool ThreadSafe { get; private set; } - public bool Notify { get; private set; } - public string[] AlsoNotify { get; private set; } - public AccessorAccessibility Getter { get; private set; } - public AccessorAccessibility Setter { get; private set; } + 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) - { - ApplyBindablePropertyAttribute(Array.Empty(), readOnly, threadSafe, notify, getter, setter); - } - - public BindablePropertyAttribute( - string alsoNotify, - bool readOnly = false, - bool threadSafe = true, - bool notify = true, - AccessorAccessibility getter = AccessorAccessibility.Public, - AccessorAccessibility setter = AccessorAccessibility.Public) - { - ApplyBindablePropertyAttribute(string.IsNullOrEmpty(alsoNotify) ? Array.Empty() : new string[1] { alsoNotify }, readOnly, threadSafe, notify, getter, setter); - } - - public BindablePropertyAttribute( - string[] alsoNotify, - bool readOnly = false, - bool threadSafe = true, - bool notify = true, - AccessorAccessibility getter = AccessorAccessibility.Public, - AccessorAccessibility setter = AccessorAccessibility.Public) - { - ApplyBindablePropertyAttribute(alsoNotify ?? Array.Empty(), readOnly, threadSafe, notify, getter, setter); - } - - public BindablePropertyAttribute( - IEnumerable alsoNotify, - bool readOnly = false, - bool threadSafe = true, - AccessorAccessibility getter = AccessorAccessibility.Public, - AccessorAccessibility setter = AccessorAccessibility.Public, - bool notify = true) - { - ApplyBindablePropertyAttribute(alsoNotify?.ToArray() ?? Array.Empty(), readOnly, threadSafe, notify, getter, setter); - } - - private void ApplyBindablePropertyAttribute( - string[] alsoNotify, - bool readOnly, - bool threadSafe, - bool notify, - AccessorAccessibility getter, - AccessorAccessibility setter) { ReadOnly = readOnly; ThreadSafe = threadSafe; Notify = notify; - AlsoNotify = alsoNotify; + AlsoNotify = alsoNotify ?? Array.Empty(); Getter = getter; Setter = setter; } From f8940872b51f135696d8cc3b7d8952213d27f8d6 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:33:22 -0400 Subject: [PATCH 32/33] Update CD.yml for Testing --- .github/workflows/CD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 913de57..f46e828 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 2.0.16 -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}}.2.0.16 - path: ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.2.0.16.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 From 203b79f17de7ba7ec1a2cce46b9f982d599ab4e4 Mon Sep 17 00:00:00 2001 From: Shawn LaMountain <98920689+ShawnLaMountain@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:44:55 -0400 Subject: [PATCH 33/33] Update CD.yml for production --- .github/workflows/CD.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index f46e828..7d19ce4 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -1,7 +1,7 @@ name: CD on: - workflow_dispatch: + # workflow_dispatch: release: types: [published] @@ -61,16 +61,16 @@ jobs: shell: pwsh - name: Create NuGet Package - 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 }} + # 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}}.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 + # 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 - # - name: Publish NuGet Package - # run: nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} + - name: Publish NuGet Package + run: nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY}}\${{ env.FILE_NAME}}.${{ github.event.release.tag_name }}.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }}