├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── merge-dependabot.yml ├── .gitignore ├── AssemblyToProcess ├── AssemblyToProcess.csproj ├── Child.cs ├── ClassWithDrivedProperties.cs ├── ClassWithIgnoredProperties.cs ├── ClassWithIndexer.cs ├── ClassWithToString.cs ├── EnumClass.cs ├── GenericClass.cs ├── GuidError.cs ├── GuidlClass.cs ├── IntCollection.cs ├── NestedClass.cs ├── NormalClass.cs ├── NormalStruct.cs ├── NullableClass.cs ├── ObjectCollection.cs ├── StringCollection.cs └── TimeClass.cs ├── CommonAssemblyInfo.cs ├── Directory.Build.props ├── ReferencedDependency ├── GuidError.cs ├── Parent.cs └── ReferencedDependency.csproj ├── Tests ├── AttributesConfiguration.cs ├── AttributesTests.cs ├── IntegrationTests.cs ├── TestHelper.cs └── Tests.csproj ├── ToString.Fody ├── ICustomAttributeProviderExtensions.cs ├── MethodReferenceExtensions.cs ├── ModuleWeaver.cs ├── PropertyDefinitionExtensions.cs ├── ToString.Fody.csproj └── TypeDefinitionExtensions.cs ├── ToString.sln ├── ToString.sln.DotSettings ├── ToString ├── IgnoreDuringToStringAttribute.cs ├── Key.snk ├── ToString.csproj └── ToStringAttribute.cs ├── appveyor.yml ├── global.json ├── license.txt ├── package_icon.png └── readme.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | 9 | [*.cs] 10 | indent_size = 4 11 | 12 | # Sort using and Import directives with System.* appearing first 13 | dotnet_sort_system_directives_first = true 14 | 15 | # Avoid "this." and "Me." if not necessary 16 | dotnet_style_qualification_for_field = false:error 17 | dotnet_style_qualification_for_property = false:error 18 | dotnet_style_qualification_for_method = false:error 19 | dotnet_style_qualification_for_event = false:error 20 | 21 | # Use language keywords instead of framework type names for type references 22 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 23 | dotnet_style_predefined_type_for_member_access = true:error 24 | 25 | # Suggest more modern language features when available 26 | dotnet_style_object_initializer = true:suggestion 27 | dotnet_style_collection_initializer = true:suggestion 28 | dotnet_style_coalesce_expression = false:suggestion 29 | dotnet_style_null_propagation = true:suggestion 30 | dotnet_style_explicit_tuple_names = true:suggestion 31 | 32 | # Prefer "var" everywhere 33 | csharp_style_var_for_built_in_types = true:error 34 | csharp_style_var_when_type_is_apparent = true:error 35 | csharp_style_var_elsewhere = true:error 36 | 37 | # Prefer method-like constructs to have a block body 38 | csharp_style_expression_bodied_methods = false:none 39 | csharp_style_expression_bodied_constructors = false:none 40 | csharp_style_expression_bodied_operators = false:none 41 | 42 | # Prefer property-like constructs to have an expression-body 43 | csharp_style_expression_bodied_properties = true:suggestion 44 | csharp_style_expression_bodied_indexers = true:suggestion 45 | csharp_style_expression_bodied_accessors = true:none 46 | 47 | # Suggest more modern language features when available 48 | csharp_style_pattern_matching_over_is_with_cast_check = true:error 49 | csharp_style_pattern_matching_over_as_with_null_check = true:error 50 | csharp_style_inlined_variable_declaration = true:suggestion 51 | csharp_style_throw_expression = true:suggestion 52 | csharp_style_conditional_delegate_call = true:suggestion 53 | 54 | # Newline settings 55 | #csharp_new_line_before_open_brace = all:error 56 | csharp_new_line_before_else = true 57 | csharp_new_line_before_catch = true 58 | csharp_new_line_before_finally = true 59 | csharp_new_line_before_members_in_object_initializers = true 60 | csharp_new_line_before_members_in_anonymous_types = true 61 | 62 | #braces 63 | #csharp_prefer_braces = true:error 64 | 65 | # msbuild 66 | [*.{csproj,targets,props}] 67 | indent_size = 2 68 | 69 | # Xml files 70 | [*.{xml,config,nuspec,resx,vsixmanifest}] 71 | indent_size = 2 72 | resharper_xml_wrap_tags_and_pi = true:error 73 | 74 | # JSON files 75 | [*.json] 76 | indent_size = 2 77 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text 3 | 4 | # Don't check these into the repo as LF to work around TeamCity bug 5 | *.xml -text 6 | *.targets -text 7 | 8 | # Custom for Visual Studio 9 | *.cs diff=csharp 10 | *.sln merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | 16 | # Denote all files that are truly binary and should not be modified. 17 | *.dll binary 18 | *.exe binary 19 | *.png binary 20 | *.ico binary 21 | *.snk binary 22 | *.pdb binary 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: Verify.Xunit 10 | versions: 11 | - 10.9.0 12 | -------------------------------------------------------------------------------- /.github/workflows/merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: merge-dependabot 2 | on: 3 | pull_request: 4 | jobs: 5 | automerge: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - name: Dependabot Auto Merge 10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6 11 | with: 12 | target: minor 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | command: squash and merge -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # Benchmark Results 46 | BenchmarkDotNet.Artifacts/ 47 | 48 | # .NET Core 49 | project.lock.json 50 | project.fragment.lock.json 51 | artifacts/ 52 | **/Properties/launchSettings.json 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # Visual Studio Trace Files 100 | *.e2e 101 | 102 | # TFS 2012 Local Workspace 103 | $tf/ 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | *.DotSettings.user 112 | 113 | # JustCode is a .NET coding add-in 114 | .JustCode 115 | 116 | # TeamCity is a build add-in 117 | _TeamCity* 118 | 119 | # DotCover is a Code Coverage Tool 120 | *.dotCover 121 | 122 | # AxoCover is a Code Coverage Tool 123 | .axoCover/* 124 | !.axoCover/settings.json 125 | 126 | # Visual Studio code coverage results 127 | *.coverage 128 | *.coveragexml 129 | 130 | # NCrunch 131 | _NCrunch_* 132 | .*crunch*.local.xml 133 | nCrunchTemp_* 134 | 135 | # MightyMoose 136 | *.mm.* 137 | AutoTest.Net/ 138 | 139 | # Web workbench (sass) 140 | .sass-cache/ 141 | 142 | # Installshield output folder 143 | [Ee]xpress/ 144 | 145 | # DocProject is a documentation generator add-in 146 | DocProject/buildhelp/ 147 | DocProject/Help/*.HxT 148 | DocProject/Help/*.HxC 149 | DocProject/Help/*.hhc 150 | DocProject/Help/*.hhk 151 | DocProject/Help/*.hhp 152 | DocProject/Help/Html2 153 | DocProject/Help/html 154 | 155 | # Click-Once directory 156 | publish/ 157 | 158 | # Publish Web Output 159 | *.[Pp]ublish.xml 160 | *.azurePubxml 161 | # Note: Comment the next line if you want to checkin your web deploy settings, 162 | # but database connection strings (with potential passwords) will be unencrypted 163 | *.pubxml 164 | *.publishproj 165 | 166 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 167 | # checkin your Azure Web App publish settings, but sensitive information contained 168 | # in these scripts will be unencrypted 169 | PublishScripts/ 170 | 171 | # NuGet Packages 172 | *.nupkg 173 | # The packages folder can be ignored because of Package Restore 174 | **/[Pp]ackages/* 175 | # except build/, which is used as an MSBuild target. 176 | !**/[Pp]ackages/build/ 177 | # Uncomment if necessary however generally it will be regenerated when needed 178 | #!**/[Pp]ackages/repositories.config 179 | # NuGet v3's project.json files produces more ignorable files 180 | *.nuget.props 181 | *.nuget.targets 182 | nugets/ 183 | 184 | # Microsoft Azure Build Output 185 | csx/ 186 | *.build.csdef 187 | 188 | # Microsoft Azure Emulator 189 | ecf/ 190 | rcf/ 191 | 192 | # Windows Store app package directories and files 193 | AppPackages/ 194 | BundleArtifacts/ 195 | Package.StoreAssociation.xml 196 | _pkginfo.txt 197 | *.appx 198 | 199 | # Visual Studio cache files 200 | # files ending in .cache can be ignored 201 | *.[Cc]ache 202 | # but keep track of directories ending in .cache 203 | !*.[Cc]ache/ 204 | 205 | # Others 206 | ClientBin/ 207 | ~$* 208 | *~ 209 | *.dbmdl 210 | *.dbproj.schemaview 211 | *.jfm 212 | *.pfx 213 | *.publishsettings 214 | orleans.codegen.cs 215 | 216 | # Since there are multiple workflows, uncomment next line to ignore bower_components 217 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 218 | #bower_components/ 219 | 220 | # RIA/Silverlight projects 221 | Generated_Code/ 222 | 223 | # Backup & report files from converting an old project file 224 | # to a newer Visual Studio version. Backup files are not needed, 225 | # because we have git ;-) 226 | _UpgradeReport_Files/ 227 | Backup*/ 228 | UpgradeLog*.XML 229 | UpgradeLog*.htm 230 | 231 | # SQL Server files 232 | *.mdf 233 | *.ldf 234 | *.ndf 235 | 236 | # Business Intelligence projects 237 | *.rdl.data 238 | *.bim.layout 239 | *.bim_*.settings 240 | 241 | # Microsoft Fakes 242 | FakesAssemblies/ 243 | 244 | # GhostDoc plugin setting file 245 | *.GhostDoc.xml 246 | 247 | # Node.js Tools for Visual Studio 248 | .ntvs_analysis.dat 249 | node_modules/ 250 | 251 | # Typescript v1 declaration files 252 | typings/ 253 | 254 | # Visual Studio 6 build log 255 | *.plg 256 | 257 | # Visual Studio 6 workspace options file 258 | *.opt 259 | 260 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 261 | *.vbw 262 | 263 | # Visual Studio LightSwitch build output 264 | **/*.HTMLClient/GeneratedArtifacts 265 | **/*.DesktopClient/GeneratedArtifacts 266 | **/*.DesktopClient/ModelManifest.xml 267 | **/*.Server/GeneratedArtifacts 268 | **/*.Server/ModelManifest.xml 269 | _Pvt_Extensions 270 | 271 | # Paket dependency manager 272 | .paket/paket.exe 273 | paket-files/ 274 | 275 | # FAKE - F# Make 276 | .fake/ 277 | 278 | # JetBrains Rider 279 | .idea/ 280 | *.sln.iml 281 | 282 | # CodeRush 283 | .cr/ 284 | 285 | # Python Tools for Visual Studio (PTVS) 286 | __pycache__/ 287 | *.pyc 288 | 289 | # Cake - Uncomment if you are using it 290 | # tools/** 291 | # !tools/packages.config 292 | 293 | # Tabs Studio 294 | *.tss 295 | 296 | # Telerik's JustMock configuration file 297 | *.jmconfig 298 | 299 | # BizTalk build output 300 | *.btp.cs 301 | *.btm.cs 302 | *.odx.cs 303 | *.xsd.cs 304 | 305 | # OpenCover UI analysis results 306 | OpenCover/ 307 | -------------------------------------------------------------------------------- /AssemblyToProcess/AssemblyToProcess.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AssemblyToProcess/Child.cs: -------------------------------------------------------------------------------- 1 | using ReferencedDependency; 2 | using System.Collections.Generic; 3 | 4 | [ToString] 5 | public class Child : Parent 6 | { 7 | public long InChild { get; set; } 8 | } 9 | 10 | [ToString] 11 | public class ComplexChild : ComplexParent 12 | { 13 | public long InChildNumber { get; set; } 14 | 15 | public string InChildText { get; set; } 16 | 17 | public IEnumerable InChildCollection { get; set; } 18 | } 19 | 20 | [ToString] 21 | public class GenericChild : GenericParent 22 | { 23 | public string InChild { get; set; } 24 | } -------------------------------------------------------------------------------- /AssemblyToProcess/ClassWithDrivedProperties.cs: -------------------------------------------------------------------------------- 1 | public abstract class SuperClass 2 | { 3 | public string NormalProperty => "Normal"; 4 | public virtual string VirtualProperty => "Virtual"; 5 | public abstract string AbstractProperty { get; } 6 | } 7 | 8 | public interface INormalProperty 9 | { 10 | string NormalProperty { get; } 11 | } 12 | 13 | [ToString] 14 | public class ClassWithDerivedProperties : SuperClass, INormalProperty 15 | { 16 | public new string NormalProperty => "New"; 17 | string INormalProperty.NormalProperty => "Interface"; 18 | public override string VirtualProperty => "Override Virtual"; 19 | public override string AbstractProperty => "Override Abstract"; 20 | } 21 | -------------------------------------------------------------------------------- /AssemblyToProcess/ClassWithIgnoredProperties.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public class ClassWithIgnoredProperties 3 | { 4 | public string Username { get; set; } 5 | 6 | public int Age { get; set; } 7 | 8 | [IgnoreDuringToString] 9 | public string Password { get; set; } 10 | } -------------------------------------------------------------------------------- /AssemblyToProcess/ClassWithIndexer.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable ValueParameterNotUsed 2 | [ToString] 3 | public class ClassWithIndexer 4 | { 5 | public int X { get; set; } 6 | 7 | public byte Y { get; set; } 8 | 9 | public int this[int index] 10 | { 11 | get => X; 12 | set => X = index; 13 | } 14 | } -------------------------------------------------------------------------------- /AssemblyToProcess/ClassWithToString.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public class ClassWithToString 3 | { 4 | public int X { get; set; } 5 | 6 | public int Y { get; set; } 7 | 8 | public override string ToString() 9 | { 10 | return "XY"; 11 | } 12 | } -------------------------------------------------------------------------------- /AssemblyToProcess/EnumClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public enum NormalEnum 4 | { 5 | A = 0, 6 | B = 1, 7 | C = 2, 8 | D = 3, 9 | E = 4, 10 | } 11 | 12 | [Flags] 13 | public enum FlagsEnum 14 | { 15 | G = 0, 16 | H = 1, 17 | I = 2, 18 | J = 4, 19 | K = 8, 20 | } 21 | 22 | [ToString] 23 | public class EnumClass 24 | { 25 | public NormalEnum NormalEnum { get; set; } 26 | 27 | public FlagsEnum FlagsEnum { get; set; } 28 | 29 | public EnumClass() 30 | { 31 | } 32 | 33 | public EnumClass(int normalEnum, int flagsEnum) 34 | { 35 | NormalEnum = (NormalEnum)normalEnum; 36 | FlagsEnum = (FlagsEnum)flagsEnum; 37 | } 38 | } -------------------------------------------------------------------------------- /AssemblyToProcess/GenericClass.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | [ToString] 4 | public class WithGenericParameter : GenericClass where T : GenericClassBaseClass 5 | { 6 | public int X { get; set; } 7 | } 8 | 9 | [ToString] 10 | public class WithoutGenericParameter : GenericClass 11 | { 12 | public int Z { get; set; } 13 | } 14 | 15 | [ToString] 16 | public class WithPropertyOfGenericType where T : GenericClassBaseClass 17 | { 18 | public T GP { get; set; } 19 | } 20 | 21 | [ToString] 22 | public class WithInheritedPropertyOfGenericType : WithPropertyOfGenericType 23 | { 24 | public int X { get; set; } 25 | } 26 | 27 | [ToString] 28 | public class GenericClass where T : GenericClassBaseClass 29 | { 30 | public int a; 31 | 32 | public int A 33 | { 34 | get => a; 35 | set => a = value; 36 | } 37 | 38 | public IEnumerable B { get; set; } 39 | } 40 | 41 | [ToString] 42 | public abstract class GenericClassBaseClass 43 | { 44 | public int C { get; set; } 45 | } 46 | 47 | [ToString] 48 | public class GenericClassNormalClass : GenericClassBaseClass 49 | { 50 | public int D { get; set; } 51 | } -------------------------------------------------------------------------------- /AssemblyToProcess/GuidError.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public class ReferenceObject : NameObject; -------------------------------------------------------------------------------- /AssemblyToProcess/GuidlClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [ToString] 4 | public class GuidClass 5 | { 6 | public int X { get; set; } 7 | 8 | public Guid Y { get; set; } 9 | } -------------------------------------------------------------------------------- /AssemblyToProcess/IntCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | [ToString] 4 | public class IntCollection 5 | { 6 | public int Count { get; set; } 7 | 8 | public IEnumerable Collection { get; set; } 9 | } -------------------------------------------------------------------------------- /AssemblyToProcess/NestedClass.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public class NestedClass 3 | { 4 | public int A { get; set; } 5 | 6 | public string B { get; set; } 7 | 8 | public double C { get; set; } 9 | 10 | public NormalClass D { get; set; } 11 | } -------------------------------------------------------------------------------- /AssemblyToProcess/NormalClass.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public class NormalClass 3 | { 4 | public int X { get; set; } 5 | 6 | public string Y { get; set; } 7 | 8 | public double Z { get; set; } 9 | 10 | public char V { get; set; } 11 | } -------------------------------------------------------------------------------- /AssemblyToProcess/NormalStruct.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public struct NormalStruct 3 | { 4 | public int X { get; set; } 5 | 6 | public string Y { get; set; } 7 | 8 | public double Z { get; set; } 9 | } -------------------------------------------------------------------------------- /AssemblyToProcess/NullableClass.cs: -------------------------------------------------------------------------------- 1 | [ToString] 2 | public class NullableClass 3 | { 4 | public int? X { get; set; } 5 | 6 | public string Y { get; set; } 7 | 8 | public double? Z { get; set; } 9 | 10 | public char? V { get; set; } 11 | } -------------------------------------------------------------------------------- /AssemblyToProcess/ObjectCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | [ToString] 4 | public class ObjectCollection 5 | { 6 | public int Count { get; set; } 7 | 8 | public IEnumerable Collection { get; set; } 9 | } -------------------------------------------------------------------------------- /AssemblyToProcess/StringCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | [ToString] 4 | public class StringCollection 5 | { 6 | public int Count { get; set; } 7 | 8 | public IEnumerable Collection { get; set; } 9 | } -------------------------------------------------------------------------------- /AssemblyToProcess/TimeClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [ToString] 4 | public class TimeClass 5 | { 6 | public DateTime X { get; set; } 7 | 8 | public TimeSpan Y { get; set; } 9 | 10 | public object z() 11 | { 12 | return X.ToUniversalTime(); 13 | } 14 | } -------------------------------------------------------------------------------- /CommonAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | [assembly: AssemblyTitle("Foody.ToString")] 3 | [assembly: AssemblyProduct("Foody.ToString")] 4 | [assembly: AssemblyVersion("1.9.0")] 5 | [assembly: AssemblyFileVersion("1.9.0")] -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | preview 6 | 1.11.1 7 | true 8 | all 9 | low 10 | 11 | -------------------------------------------------------------------------------- /ReferencedDependency/GuidError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class StatePrinter 4 | { 5 | public string PrintObject( object o ) 6 | { 7 | return o.ToString(); 8 | } 9 | } 10 | 11 | public class DomainObject 12 | { 13 | static readonly StatePrinter StatePrinter = new(); 14 | 15 | public override string ToString() 16 | { 17 | return StatePrinter.PrintObject( this ); 18 | } 19 | } 20 | 21 | public class IdObject : DomainObject 22 | { 23 | public Guid Id { get; set; } 24 | 25 | public bool IsEmpty() 26 | { 27 | return Id == Guid.Empty; 28 | } 29 | } 30 | 31 | public class NameObject : IdObject 32 | { 33 | public string Name { get; set; } 34 | } -------------------------------------------------------------------------------- /ReferencedDependency/Parent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ReferencedDependency; 4 | 5 | public class Parent 6 | { 7 | public long InParent { get; set; } 8 | } 9 | 10 | public class ComplexParent 11 | { 12 | public long InParentNumber { get; set; } 13 | 14 | public string InParentText { get; set; } 15 | 16 | public IEnumerable InParentCollection { get; set; } 17 | } 18 | 19 | public class GenericParent 20 | { 21 | public T GenericInParent { get; set; } 22 | } -------------------------------------------------------------------------------- /ReferencedDependency/ReferencedDependency.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tests/AttributesConfiguration.cs: -------------------------------------------------------------------------------- 1 | public class AttributesConfiguration 2 | { 3 | public string PropertyNameToValueSeparator; 4 | public string PropertiesSeparator; 5 | public bool? WrapWithBrackets; 6 | public bool? WriteTypeName; 7 | public string ListStart; 8 | public string ListEnd; 9 | } -------------------------------------------------------------------------------- /Tests/AttributesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Fody; 4 | using Xunit; 5 | 6 | public class AttributesTests 7 | { 8 | string PropertyNameToValueSeparator = "$%^%$"; 9 | string PropertiesSeparator = "$@#@$"; 10 | bool WrapWithBrackets = false; 11 | bool WriteTypeName = false; 12 | string ListStart = "---[[["; 13 | string ListEnd = "]]]---"; 14 | 15 | public Assembly PrepareAssembly(string name, AttributesConfiguration configuration) 16 | { 17 | var config = TestHelper.PrepareConfig(configuration); 18 | var weaver = new ModuleWeaver 19 | { 20 | Config = config 21 | 22 | }; 23 | var testResult = weaver.ExecuteTestRun("AssemblyToProcess.dll",assemblyName:name); 24 | return testResult.Assembly; 25 | } 26 | 27 | [Fact] 28 | public void NormalClassTest_ShouldUseCustomPropertyNameToValueSeparator() 29 | { 30 | var assembly = PrepareAssembly( 31 | "test1", 32 | new() 33 | { 34 | PropertyNameToValueSeparator = PropertyNameToValueSeparator 35 | }); 36 | 37 | var type = assembly.GetType("NormalClass"); 38 | dynamic instance = Activator.CreateInstance(type); 39 | instance.X = 1; 40 | instance.Y = "2"; 41 | instance.Z = 4.5; 42 | instance.V = 'C'; 43 | 44 | var result = instance.ToString(); 45 | 46 | Assert.Equal( 47 | string.Format("{{T{0}\"NormalClass\", X{0}1, Y{0}\"2\", Z{0}4.5, V{0}\"C\"}}", PropertyNameToValueSeparator), 48 | result); 49 | } 50 | 51 | [Fact] 52 | public void NormalClassTest_ShouldUseCustomPropertiesSeparator() 53 | { 54 | var assembly = PrepareAssembly("test2", 55 | new() 56 | { 57 | PropertiesSeparator = PropertiesSeparator 58 | }); 59 | 60 | var type = assembly.GetType("NormalClass"); 61 | dynamic instance = Activator.CreateInstance(type); 62 | instance.X = 1; 63 | instance.Y = "2"; 64 | instance.Z = 4.5; 65 | instance.V = 'C'; 66 | 67 | var result = instance.ToString(); 68 | 69 | Assert.Equal( 70 | string.Format("{{T: \"NormalClass\"{0}X: 1{0}Y: \"2\"{0}Z: 4.5{0}V: \"C\"}}", PropertiesSeparator), 71 | result); 72 | } 73 | 74 | [Fact] 75 | public void NormalClassTest_ShouldNotWrapInBrackets() 76 | { 77 | var assembly = PrepareAssembly("test3", 78 | new() 79 | { 80 | WrapWithBrackets = WrapWithBrackets 81 | }); 82 | 83 | var type = assembly.GetType("NormalClass"); 84 | dynamic instance = Activator.CreateInstance(type); 85 | instance.X = 1; 86 | instance.Y = "2"; 87 | instance.Z = 4.5; 88 | instance.V = 'C'; 89 | 90 | var result = instance.ToString(); 91 | 92 | Assert.Equal( 93 | "T: \"NormalClass\", X: 1, Y: \"2\", Z: 4.5, V: \"C\"", 94 | result); 95 | } 96 | 97 | [Fact] 98 | public void NormalClassTest_ShouldNotWriteClassName() 99 | { 100 | var assembly = PrepareAssembly("test4", 101 | new() 102 | { 103 | WriteTypeName = WriteTypeName 104 | }); 105 | 106 | var type = assembly.GetType("NormalClass"); 107 | dynamic instance = Activator.CreateInstance(type); 108 | instance.X = 1; 109 | instance.Y = "2"; 110 | instance.Z = 4.5; 111 | instance.V = 'C'; 112 | 113 | var result = instance.ToString(); 114 | 115 | Assert.Equal( 116 | "{X: 1, Y: \"2\", Z: 4.5, V: \"C\"}", 117 | result); 118 | } 119 | 120 | [Fact] 121 | public void NormalClassTest_ShouldStartListWithCustomSeparator() 122 | { 123 | var assembly = PrepareAssembly("test5", 124 | new() 125 | { 126 | ListStart = ListStart 127 | }); 128 | 129 | var type = assembly.GetType("IntCollection"); 130 | dynamic instance = Activator.CreateInstance(type); 131 | instance.Collection = new[] {1, 2, 3, 4, 5, 6}; 132 | instance.Count = 2; 133 | 134 | var result = instance.ToString(); 135 | 136 | var expected = $"{{T: \"IntCollection\", Count: 2, Collection: {ListStart}1, 2, 3, 4, 5, 6]}}"; 137 | 138 | Assert.Equal(expected, result); 139 | } 140 | 141 | [Fact] 142 | public void NormalClassTest_ShouldEndListWithCustomSeparator() 143 | { 144 | var assembly = PrepareAssembly("test6", 145 | new() 146 | { 147 | ListEnd = ListEnd 148 | }); 149 | 150 | var type = assembly.GetType("IntCollection"); 151 | dynamic instance = Activator.CreateInstance(type); 152 | instance.Collection = new[] {1, 2, 3, 4, 5, 6}; 153 | instance.Count = 2; 154 | 155 | var result = instance.ToString(); 156 | 157 | var expected = $"{{T: \"IntCollection\", Count: 2, Collection: [1, 2, 3, 4, 5, 6{ListEnd}}}"; 158 | 159 | Assert.Equal(expected, result); 160 | } 161 | } -------------------------------------------------------------------------------- /Tests/IntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using Fody; 5 | using Xunit; 6 | 7 | public class IntegrationTests 8 | { 9 | static Assembly assembly; 10 | static TestResult testResult; 11 | 12 | static IntegrationTests() 13 | { 14 | var weaver = new ModuleWeaver(); 15 | testResult = weaver.ExecuteTestRun("AssemblyToProcess.dll"); 16 | assembly = testResult.Assembly; 17 | } 18 | 19 | [Fact] 20 | public void NormalClassTest() 21 | { 22 | var instance = testResult.GetInstance("NormalClass"); 23 | instance.X = 1; 24 | instance.Y = "2"; 25 | instance.Z = 4.5; 26 | instance.V = 'C'; 27 | 28 | var result = instance.ToString(); 29 | 30 | Assert.Equal("{T: \"NormalClass\", X: 1, Y: \"2\", Z: 4.5, V: \"C\"}", result); 31 | } 32 | 33 | [Fact] 34 | public void NormalStructTest() 35 | { 36 | var instance = testResult.GetInstance("NormalStruct"); 37 | instance.X = 1; 38 | instance.Y = "2"; 39 | instance.Z = 4.5; 40 | 41 | var result = instance.ToString(); 42 | 43 | Assert.Equal("{T: \"NormalStruct\", X: 1, Y: \"2\", Z: 4.5}", result); 44 | } 45 | 46 | [Fact] 47 | public void NestedClassTest() 48 | { 49 | var normalInstance = testResult.GetInstance("NormalClass"); 50 | normalInstance.X = 1; 51 | normalInstance.Y = "2"; 52 | normalInstance.Z = 4.5; 53 | normalInstance.V = 'V'; 54 | var nestedInstance = testResult.GetInstance("NestedClass"); 55 | nestedInstance.A = 10; 56 | nestedInstance.B = "11"; 57 | nestedInstance.C = 12.25; 58 | nestedInstance.D = normalInstance; 59 | 60 | var result = nestedInstance.ToString(); 61 | 62 | Assert.Equal("{T: \"NestedClass\", A: 10, B: \"11\", C: 12.25, D: {T: \"NormalClass\", X: 1, Y: \"2\", Z: 4.5, V: \"V\"}}", result); 63 | } 64 | 65 | [Fact] 66 | public void ClassWithIgnoredPropertiesTest() 67 | { 68 | var type = assembly.GetType("ClassWithIgnoredProperties"); 69 | dynamic instance = Activator.CreateInstance(type); 70 | instance.Username = "user"; 71 | instance.Password = "pass"; 72 | instance.Age = 18; 73 | 74 | var result = instance.ToString(); 75 | 76 | Assert.Equal("{T: \"ClassWithIgnoredProperties\", Username: \"user\", Age: 18}", result); 77 | } 78 | 79 | [Fact] 80 | public void NullTest() 81 | { 82 | var nestedType = assembly.GetType("NestedClass"); 83 | dynamic nestedInstance = Activator.CreateInstance(nestedType); 84 | nestedInstance.A = 10; 85 | nestedInstance.B = "11"; 86 | nestedInstance.C = 12.25; 87 | nestedInstance.D = null; 88 | 89 | var result = nestedInstance.ToString(); 90 | 91 | Assert.Equal("{T: \"NestedClass\", A: 10, B: \"11\", C: 12.25, D: null}", result); 92 | } 93 | 94 | [Fact] 95 | public void ClassWithParentInAnotherAssembly() 96 | { 97 | var derivedType = assembly.GetType("Child"); 98 | dynamic instance = Activator.CreateInstance(derivedType); 99 | instance.InParent = 10; 100 | instance.InChild = 5; 101 | 102 | var result = instance.ToString(); 103 | 104 | Assert.Equal(result, "{T: \"Child\", InChild: 5, InParent: 10}"); 105 | } 106 | 107 | [Fact] 108 | public void ComplexClassWithParentInAnotherAssembly() 109 | { 110 | var derivedType = assembly.GetType("ComplexChild"); 111 | dynamic instance = Activator.CreateInstance(derivedType); 112 | instance.InChildNumber = 1L; 113 | instance.InChildText = "2"; 114 | instance.InChildCollection = new[] {3}; 115 | instance.InParentNumber = 4L; 116 | instance.InParentText = "5"; 117 | instance.InParentCollection = new[] {6}; 118 | 119 | var result = instance.ToString(); 120 | 121 | Assert.Equal(result, "{T: \"ComplexChild\", InChildNumber: 1, InChildText: \"2\", InChildCollection: [3], InParentNumber: 4, InParentText: \"5\", InParentCollection: [6]}"); 122 | } 123 | 124 | [Fact] 125 | public void ClassWithGenericParentInAnotherAssembly() 126 | { 127 | var derivedType = assembly.GetType("GenericChild"); 128 | dynamic instance = Activator.CreateInstance(derivedType); 129 | instance.InChild = "5"; 130 | instance.GenericInParent = 6; 131 | 132 | var result = instance.ToString(); 133 | 134 | Assert.Equal(result, "{T: \"GenericChild\", InChild: \"5\", GenericInParent: 6}"); 135 | } 136 | 137 | [Fact] 138 | public void GuidErrorTest() 139 | { 140 | var type = assembly.GetType( "ReferenceObject" ); 141 | dynamic instance = Activator.CreateInstance( type ); 142 | instance.Id = Guid.Parse( "{f6ab1abe-5811-40e9-8154-35776d2e5106}" ); 143 | instance.Name = "Test"; 144 | 145 | var result = instance.ToString(); 146 | 147 | Assert.Equal( "{T: \"ReferenceObject\", Name: \"Test\", Id: \"f6ab1abe-5811-40e9-8154-35776d2e5106\"}", result ); 148 | } 149 | 150 | #region Collections 151 | 152 | [Fact] 153 | public void IntArray() 154 | { 155 | var type = assembly.GetType("IntCollection"); 156 | dynamic nestedInstance = Activator.CreateInstance(type); 157 | nestedInstance.Collection = new[] { 1, 2, 3, 4, 5, 6 }; 158 | nestedInstance.Count = 2; 159 | 160 | var result = nestedInstance.ToString(); 161 | 162 | Assert.Equal("{T: \"IntCollection\", Count: 2, Collection: [1, 2, 3, 4, 5, 6]}", result); 163 | } 164 | 165 | [Fact] 166 | public void StringArray() 167 | { 168 | var type = assembly.GetType("StringCollection"); 169 | dynamic nestedInstance = Activator.CreateInstance(type); 170 | nestedInstance.Collection = new List { "foo", "bar" }; 171 | nestedInstance.Count = 2; 172 | 173 | var result = nestedInstance.ToString(); 174 | 175 | Assert.Equal("{T: \"StringCollection\", Count: 2, Collection: [\"foo\", \"bar\"]}", result); 176 | } 177 | 178 | [Fact] 179 | public void EmptyArray() 180 | { 181 | var type = assembly.GetType("IntCollection"); 182 | dynamic nestedInstance = Activator.CreateInstance(type); 183 | nestedInstance.Collection = new int[] {}; 184 | nestedInstance.Count = 0; 185 | 186 | var result = nestedInstance.ToString(); 187 | 188 | Assert.Equal("{T: \"IntCollection\", Count: 0, Collection: []}", result); 189 | } 190 | 191 | [Fact] 192 | public void NullArray() 193 | { 194 | var type = assembly.GetType("IntCollection"); 195 | dynamic nestedInstance = Activator.CreateInstance(type); 196 | nestedInstance.Collection = null; 197 | nestedInstance.Count = 0; 198 | 199 | var result = nestedInstance.ToString(); 200 | 201 | Assert.Equal("{T: \"IntCollection\", Count: 0, Collection: null}", result); 202 | } 203 | 204 | [Fact] 205 | public void ObjectArray() 206 | { 207 | var arrayType = assembly.GetType("ObjectCollection"); 208 | dynamic arrayInstance = Activator.CreateInstance(arrayType); 209 | arrayInstance.Count = 2; 210 | 211 | var type = assembly.GetType("NormalClass"); 212 | dynamic instance = Activator.CreateInstance(type); 213 | instance.X = 1; 214 | instance.Y = "2"; 215 | instance.Z = 4.5; 216 | instance.V = 'C'; 217 | 218 | dynamic array = Activator.CreateInstance(type.MakeArrayType(), 2); 219 | array[0] = instance; 220 | array[1] = null; 221 | 222 | arrayInstance.Collection = array; 223 | 224 | var result = arrayInstance.ToString(); 225 | 226 | Assert.Equal("{T: \"ObjectCollection\", Count: 2, Collection: [{T: \"NormalClass\", X: 1, Y: \"2\", Z: 4.5, V: \"C\"}, null]}", result); 227 | } 228 | 229 | [Fact] 230 | public void GenericClassWithCollection() 231 | { 232 | var genericClassType = assembly.GetType("GenericClass`1"); 233 | var propType = assembly.GetType("GenericClassNormalClass"); 234 | var instanceType = genericClassType.MakeGenericType(propType); 235 | 236 | dynamic instance = Activator.CreateInstance(instanceType); 237 | instance.A = 1; 238 | 239 | dynamic propInstance = Activator.CreateInstance(propType); 240 | propInstance.D = 2; 241 | propInstance.C = 3; 242 | 243 | dynamic array = Activator.CreateInstance(propType.MakeArrayType(), 1); 244 | array[0] = propInstance; 245 | 246 | instance.B = array; 247 | 248 | var result = instance.ToString(); 249 | 250 | Assert.Equal("{T: \"GenericClass\", A: 1, B: [{T: \"GenericClassNormalClass\", D: 2, C: 3}]}", result); 251 | } 252 | 253 | [Fact] 254 | public void WithoutGenericParameter() 255 | { 256 | var withoutGenericParameterType = assembly.GetType("WithoutGenericParameter"); 257 | var propType = assembly.GetType("GenericClassNormalClass"); 258 | 259 | dynamic instance = Activator.CreateInstance(withoutGenericParameterType); 260 | instance.Z = 12; 261 | instance.A = 1; 262 | dynamic propInstance = Activator.CreateInstance(propType); 263 | propInstance.D = 3; 264 | propInstance.C = -4; 265 | dynamic array = Activator.CreateInstance(propType.MakeArrayType(), 1); 266 | array[0] = propInstance; 267 | instance.B = array; 268 | 269 | var result = instance.ToString(); 270 | 271 | Assert.Equal("{T: \"WithoutGenericParameter\", Z: 12, A: 1, B: [{T: \"GenericClassNormalClass\", D: 3, C: -4}]}", result); 272 | } 273 | 274 | [Fact] 275 | public void WithGenericParameter() 276 | { 277 | var withGenericParameterType = assembly.GetType("WithGenericParameter`1"); 278 | var propType = assembly.GetType("GenericClassNormalClass"); 279 | var instanceType = withGenericParameterType.MakeGenericType(propType); 280 | 281 | dynamic instance = Activator.CreateInstance(instanceType); 282 | instance.X = 12; 283 | instance.A = 1; 284 | dynamic propInstance = Activator.CreateInstance(propType); 285 | propInstance.D = 3; 286 | propInstance.C = 4; 287 | dynamic array = Activator.CreateInstance(propType.MakeArrayType(), 1); 288 | array[0] = propInstance; 289 | instance.B = array; 290 | 291 | var result = instance.ToString(); 292 | 293 | Assert.Equal("{T: \"WithGenericParameter\", X: 12, A: 1, B: [{T: \"GenericClassNormalClass\", D: 3, C: 4}]}", result); 294 | } 295 | 296 | [Fact] 297 | public void WithGenericProperty() 298 | { 299 | var withGenericPropertyType = assembly.GetType("WithPropertyOfGenericType`1"); 300 | var propType = assembly.GetType("GenericClassNormalClass"); 301 | var instanceType = withGenericPropertyType.MakeGenericType(propType); 302 | 303 | dynamic instance = Activator.CreateInstance(instanceType); 304 | dynamic propInstance = Activator.CreateInstance(propType); 305 | instance.GP = propInstance; 306 | propInstance.C = 1; 307 | propInstance.D = 3; 308 | 309 | var result = instance.ToString(); 310 | 311 | Assert.Equal(result, "{T: \"WithPropertyOfGenericType\", GP: {T: \"GenericClassNormalClass\", D: 3, C: 1}}"); 312 | } 313 | 314 | [Fact] 315 | public void WithInheritedGenericProperty() 316 | { 317 | var withGenericPropertyType = assembly.GetType("WithInheritedPropertyOfGenericType"); 318 | 319 | dynamic instance = Activator.CreateInstance(withGenericPropertyType); 320 | var propType = assembly.GetType("GenericClassNormalClass"); 321 | dynamic propInstance = Activator.CreateInstance(propType); 322 | instance.GP = propInstance; 323 | propInstance.C = 1; 324 | propInstance.D = 3; 325 | instance.X = 6; 326 | 327 | var result = instance.ToString(); 328 | 329 | Assert.Equal(result, "{T: \"WithInheritedPropertyOfGenericType\", X: 6, GP: {T: \"GenericClassNormalClass\", D: 3, C: 1}}"); 330 | } 331 | 332 | #endregion 333 | 334 | #region enums 335 | 336 | [Fact] 337 | public void EmptyEnum() 338 | { 339 | var type = assembly.GetType("EnumClass"); 340 | dynamic instance = Activator.CreateInstance(type); 341 | 342 | var result = instance.ToString(); 343 | 344 | Assert.Equal("{T: \"EnumClass\", NormalEnum: \"A\", FlagsEnum: \"G\"}", result); 345 | } 346 | 347 | [Fact] 348 | public void EnumWithValues() 349 | { 350 | var type = assembly.GetType("EnumClass"); 351 | dynamic instance = Activator.CreateInstance(type, 3, 6); 352 | 353 | var result = instance.ToString(); 354 | 355 | Assert.Equal("{T: \"EnumClass\", NormalEnum: \"D\", FlagsEnum: \"I, J\"}", result); 356 | } 357 | 358 | 359 | #endregion 360 | 361 | [Fact] 362 | public void TimeClassTest() 363 | { 364 | var type = assembly.GetType( "TimeClass" ); 365 | dynamic instance = Activator.CreateInstance( type ); 366 | instance.X = new DateTime(1988, 05, 23, 10, 30, 0, DateTimeKind.Utc); 367 | instance.Y = new TimeSpan(1, 2, 3, 4); 368 | 369 | var result = instance.ToString(); 370 | 371 | Assert.Equal( "{T: \"TimeClass\", X: \"1988-05-23T10:30:00.0000000Z\", Y: \"1.02:03:04\"}", result ); 372 | } 373 | 374 | [Fact] 375 | public void IndexerTest() 376 | { 377 | var type = assembly.GetType("ClassWithIndexer"); 378 | dynamic instance = Activator.CreateInstance(type); 379 | instance.X = 1; 380 | instance.Y = 2; 381 | 382 | var result = instance.ToString(); 383 | 384 | Assert.Equal("{T: \"ClassWithIndexer\", X: 1, Y: 2}", result); 385 | } 386 | 387 | [Fact] 388 | public void RemoveToStringMethod() 389 | { 390 | var type = assembly.GetType("ClassWithToString"); 391 | dynamic instance = Activator.CreateInstance(type); 392 | instance.X = 1; 393 | instance.Y = 2; 394 | 395 | var result = instance.ToString(); 396 | 397 | Assert.Equal("{T: \"ClassWithToString\", X: 1, Y: 2}", result); 398 | } 399 | 400 | [Fact] 401 | public void GuidClassTest() 402 | { 403 | var type = assembly.GetType( "GuidClass" ); 404 | dynamic instance = Activator.CreateInstance( type ); 405 | instance.X = 1; 406 | instance.Y = new Guid(1,2,3,4,5,6,7,8,9,10,11); 407 | 408 | var result = instance.ToString(); 409 | 410 | Assert.Equal( "{T: \"GuidClass\", X: 1, Y: \"00000001-0002-0003-0405-060708090a0b\"}", result ); 411 | } 412 | 413 | [Fact] 414 | public void ClassWithDerivedPropertiesTest() 415 | { 416 | var type = assembly.GetType("ClassWithDerivedProperties"); 417 | dynamic instance = Activator.CreateInstance(type); 418 | var result = instance.ToString(); 419 | 420 | Assert.Equal("{T: \"ClassWithDerivedProperties\", NormalProperty: \"New\", INormalProperty.NormalProperty: \"Interface\", VirtualProperty: \"Override Virtual\", AbstractProperty: \"Override Abstract\"}", result); 421 | } 422 | } -------------------------------------------------------------------------------- /Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Xml.Linq; 3 | 4 | static class TestHelper 5 | { 6 | public static XElement PrepareConfig(AttributesConfiguration configuration) 7 | { 8 | var configXml = new StringBuilder(); 9 | configXml.Append(""); 41 | 42 | return XElement.Parse(configXml.ToString()); 43 | } 44 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48;net8.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ToString.Fody/ICustomAttributeProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | using Mono.Cecil; 4 | 5 | public static class ICustomAttributeProviderExtensions 6 | { 7 | public static void RemoveAttribute(this ICustomAttributeProvider definition, string name) 8 | { 9 | var customAttributes = definition.CustomAttributes; 10 | 11 | var attribute = customAttributes.FirstOrDefault(_ => _.AttributeType.Name == name); 12 | 13 | if (attribute != null) 14 | { 15 | customAttributes.Remove(attribute); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ToString.Fody/MethodReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | public static class MethodReferenceExtensions 4 | { 5 | public static bool IsMatch(this MethodReference methodReference, params string[] paramTypes) 6 | { 7 | var parameters = methodReference.Parameters; 8 | if (parameters.Count != paramTypes.Length) 9 | { 10 | return false; 11 | } 12 | 13 | var methodReferenceParameters = parameters; 14 | for (var index = 0; index < methodReferenceParameters.Count; index++) 15 | { 16 | var parameterDefinition = methodReferenceParameters[index]; 17 | var paramType = paramTypes[index]; 18 | if (parameterDefinition.ParameterType.Name != paramType) 19 | { 20 | return false; 21 | } 22 | } 23 | 24 | return true; 25 | } 26 | } -------------------------------------------------------------------------------- /ToString.Fody/ModuleWeaver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Text; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | using Mono.Cecil; 7 | using Mono.Cecil.Cil; 8 | using Mono.Cecil.Rocks; 9 | using Mono.Collections.Generic; 10 | using System.CodeDom.Compiler; 11 | using System.Diagnostics; 12 | using Fody; 13 | 14 | public class ModuleWeaver : BaseModuleWeaver 15 | { 16 | TypeReference stringBuilderType; 17 | MethodReference appendString; 18 | MethodReference moveNext; 19 | MethodReference current; 20 | MethodReference getEnumerator; 21 | MethodReference getInvariantCulture; 22 | MethodReference formatMethod; 23 | 24 | public IEnumerable GetMatchingTypes() 25 | { 26 | return ModuleDefinition.GetTypes() 27 | .Where(_ => _.CustomAttributes.Any(a => a.AttributeType.Name == "ToStringAttribute")); 28 | } 29 | 30 | public override IEnumerable GetAssembliesForScanning() 31 | { 32 | yield return "mscorlib"; 33 | yield return "System"; 34 | yield return "System.Runtime"; 35 | yield return "netstandard"; 36 | } 37 | 38 | public override void Execute() 39 | { 40 | var stringBuildType = FindTypeDefinition("System.Text.StringBuilder"); 41 | stringBuilderType = ModuleDefinition.ImportReference(stringBuildType); 42 | appendString = ModuleDefinition.ImportReference(stringBuildType.FindMethod("Append", "Object")); 43 | var enumeratorType = FindTypeDefinition("System.Collections.IEnumerator"); 44 | moveNext = ModuleDefinition.ImportReference(enumeratorType.FindMethod("MoveNext")); 45 | current = ModuleDefinition.ImportReference(enumeratorType.Properties.Single(_ => _.Name == "Current").GetMethod); 46 | var enumerableType = FindTypeDefinition("System.Collections.IEnumerable"); 47 | getEnumerator = ModuleDefinition.ImportReference(enumerableType.FindMethod("GetEnumerator")); 48 | formatMethod = ModuleDefinition.ImportReference(TypeSystem.StringDefinition.FindMethod("Format", "IFormatProvider", "String", "Object[]")); 49 | 50 | var cultureInfoType = FindTypeDefinition("System.Globalization.CultureInfo"); 51 | var invariantCulture = cultureInfoType.Properties.Single(_ => _.Name == "InvariantCulture"); 52 | getInvariantCulture = ModuleDefinition.ImportReference(invariantCulture.GetMethod); 53 | 54 | foreach (var type in GetMatchingTypes()) 55 | { 56 | AddToString(type); 57 | } 58 | } 59 | 60 | void AddToString(TypeDefinition type) 61 | { 62 | var methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual; 63 | 64 | var method = new MethodDefinition("ToString", methodAttributes, TypeSystem.StringReference); 65 | var variables = method.Body.Variables; 66 | variables.Add(new(new ArrayType(TypeSystem.ObjectReference))); 67 | var allProperties = type.GetProperties().Where(x => !x.HasParameters).ToArray(); 68 | var properties = RemoveIgnoredProperties(allProperties).Distinct(PropertyNameEqualityComparer.Default).ToArray(); 69 | 70 | var format = GetFormatString(type, properties); 71 | 72 | var body = method.Body; 73 | body.InitLocals = true; 74 | var ins = body.Instructions; 75 | 76 | var hasCollections = properties.Any(x => !x.PropertyType.IsGenericParameter && x.PropertyType.Resolve().IsCollection()); 77 | if (hasCollections) 78 | { 79 | variables.Add(new(stringBuilderType)); 80 | 81 | var enumeratorType = ModuleDefinition.ImportReference(typeof(IEnumerator)); 82 | variables.Add(new(enumeratorType)); 83 | 84 | variables.Add(new(TypeSystem.BooleanReference)); 85 | 86 | variables.Add(new(new ArrayType(TypeSystem.ObjectReference))); 87 | } 88 | 89 | var genericOffset = !type.HasGenericParameters ? 0 : type.GenericParameters.Count; 90 | AddInitCode(ins, format, properties, genericOffset); 91 | 92 | if (type.HasGenericParameters) 93 | { 94 | AddGenericParameterNames(type, ins); 95 | } 96 | 97 | for (var i = 0; i < properties.Length; i++) 98 | { 99 | var property = properties[i]; 100 | AddPropertyCode(method.Body, i + genericOffset, property, type, variables); 101 | } 102 | 103 | AddMethodAttributes(method); 104 | 105 | AddEndCode(body); 106 | body.OptimizeMacros(); 107 | 108 | var toRemove = type.Methods.FirstOrDefault(_ => _.Name == method.Name && 109 | _.Parameters.Count == 0); 110 | if (toRemove != null) 111 | { 112 | type.Methods.Remove(toRemove); 113 | } 114 | 115 | type.Methods.Add(method); 116 | 117 | RemoveFodyAttributes(type, allProperties); 118 | } 119 | 120 | void AddGenericParameterNames(TypeDefinition type, Collection ins) 121 | { 122 | var typeType = ModuleDefinition.ImportReference(FindTypeDefinition(typeof(Type).FullName!)).Resolve(); 123 | var memberInfoType = ModuleDefinition.ImportReference(FindTypeDefinition(typeof(System.Reflection.MemberInfo).FullName!)).Resolve(); 124 | var getTypeMethod = ModuleDefinition.ImportReference(TypeSystem.ObjectDefinition.FindMethod("GetType")); 125 | var getGenericArgumentsMethod = ModuleDefinition.ImportReference(typeType.FindMethod("GetGenericArguments")); 126 | var nameProperty = memberInfoType.Properties.Single(_ => _.Name == "Name"); 127 | var nameGet = ModuleDefinition.ImportReference(nameProperty.GetMethod); 128 | 129 | for (var i = 0; i < type.GenericParameters.Count; i++) 130 | { 131 | ins.Add(Instruction.Create(OpCodes.Ldloc_0)); 132 | ins.Add(Instruction.Create(OpCodes.Ldc_I4, i)); 133 | 134 | ins.Add(Instruction.Create(OpCodes.Ldarg_0)); 135 | ins.Add(Instruction.Create(OpCodes.Callvirt, getTypeMethod)); 136 | ins.Add(Instruction.Create(OpCodes.Callvirt, getGenericArgumentsMethod)); 137 | ins.Add(Instruction.Create(OpCodes.Ldc_I4, i)); 138 | ins.Add(Instruction.Create(OpCodes.Ldelem_Ref)); 139 | ins.Add(Instruction.Create(OpCodes.Callvirt, nameGet)); 140 | 141 | ins.Add(Instruction.Create(OpCodes.Stelem_Ref)); 142 | } 143 | } 144 | 145 | void AddMethodAttributes(MethodDefinition method) 146 | { 147 | var generatedConstructor = ModuleDefinition.ImportReference(typeof(GeneratedCodeAttribute) 148 | .GetConstructor([typeof(string), typeof(string)])); 149 | 150 | var version = typeof(ModuleWeaver).Assembly.GetName().Version.ToString(); 151 | 152 | var generatedAttribute = new CustomAttribute(generatedConstructor); 153 | generatedAttribute.ConstructorArguments.Add(new(TypeSystem.StringReference, "Fody.ToString")); 154 | generatedAttribute.ConstructorArguments.Add(new(TypeSystem.StringReference, version)); 155 | method.CustomAttributes.Add(generatedAttribute); 156 | 157 | var debuggerConstructor = ModuleDefinition.ImportReference(typeof(DebuggerNonUserCodeAttribute).GetConstructor(Type.EmptyTypes)); 158 | var debuggerAttribute = new CustomAttribute(debuggerConstructor); 159 | method.CustomAttributes.Add(debuggerAttribute); 160 | } 161 | 162 | void AddEndCode(MethodBody body) 163 | { 164 | body.Instructions.Add(Instruction.Create(OpCodes.Ldloc_0)); 165 | body.Instructions.Add(Instruction.Create(OpCodes.Call, formatMethod)); 166 | body.Instructions.Add(Instruction.Create(OpCodes.Ret)); 167 | } 168 | 169 | void AddInitCode(Collection ins, string format, PropertyDefinition[] properties, int genericOffset) 170 | { 171 | ins.Add(Instruction.Create(OpCodes.Call, getInvariantCulture)); 172 | ins.Add(Instruction.Create(OpCodes.Ldstr, format)); 173 | ins.Add(Instruction.Create(OpCodes.Ldc_I4, properties.Length + genericOffset)); 174 | ins.Add(Instruction.Create(OpCodes.Newarr, TypeSystem.ObjectReference)); 175 | ins.Add(Instruction.Create(OpCodes.Stloc_0)); 176 | } 177 | 178 | void AddPropertyCode(MethodBody body, int index, PropertyDefinition property, TypeDefinition targetType, Collection variables) 179 | { 180 | var ins = body.Instructions; 181 | 182 | ins.Add(Instruction.Create(OpCodes.Ldloc_0)); 183 | ins.Add(Instruction.Create(OpCodes.Ldc_I4, index)); 184 | 185 | var get = ModuleDefinition.ImportReference(property.GetGetMethod(targetType)); 186 | 187 | ins.Add(Instruction.Create(OpCodes.Ldarg_0)); 188 | ins.Add(Instruction.Create(OpCodes.Call, get)); 189 | 190 | if (get.ReturnType.IsValueType) 191 | { 192 | var returnType = ModuleDefinition.ImportReference(property.GetMethod.ReturnType); 193 | if (returnType.FullName == "System.DateTime") 194 | { 195 | var convertToUtc = ModuleDefinition.ImportReference(returnType.Resolve().FindMethod("ToUniversalTime")); 196 | 197 | var variable = new VariableDefinition(returnType); 198 | variables.Add(variable); 199 | ins.Add(Instruction.Create(OpCodes.Stloc, variable)); 200 | ins.Add(Instruction.Create(OpCodes.Ldloca, variable)); 201 | ins.Add(Instruction.Create(OpCodes.Call, convertToUtc)); 202 | } 203 | 204 | ins.Add(Instruction.Create(OpCodes.Box, returnType)); 205 | } 206 | else 207 | { 208 | var propType = property.PropertyType.Resolve(); 209 | var isCollection = !property.PropertyType.IsGenericParameter && propType.IsCollection(); 210 | 211 | if (isCollection) 212 | { 213 | AssignFalseToFirstFLag(ins); 214 | 215 | If(ins, 216 | nc => nc.Add(Instruction.Create(OpCodes.Dup)), 217 | nt => 218 | { 219 | GetEnumerator(nt); 220 | 221 | NewStringBuilder(nt); 222 | 223 | AppendString(nt, ListStart); 224 | 225 | While(nt, 226 | c => 227 | { 228 | c.Add(Instruction.Create(OpCodes.Ldloc_2)); 229 | c.Add(Instruction.Create(OpCodes.Callvirt, moveNext)); 230 | }, 231 | b => 232 | { 233 | AppendSeparator(b); 234 | 235 | ins.Add(Instruction.Create(OpCodes.Ldloc_1)); 236 | If(ins, 237 | c => 238 | { 239 | c.Add(Instruction.Create(OpCodes.Ldloc_2)); 240 | c.Add(Instruction.Create(OpCodes.Callvirt, current)); 241 | }, 242 | t => 243 | { 244 | t.Add(Instruction.Create(OpCodes.Call, getInvariantCulture)); 245 | 246 | string format; 247 | var collectionType = ((GenericInstanceType)property.PropertyType).GenericArguments[0]; 248 | if (HaveToAddQuotes(collectionType)) 249 | { 250 | format = "\"{0}\""; 251 | } 252 | else 253 | { 254 | format = "{0}"; 255 | } 256 | 257 | t.Add(Instruction.Create(OpCodes.Ldstr, format)); 258 | 259 | t.Add(Instruction.Create(OpCodes.Ldc_I4, 1)); 260 | t.Add(Instruction.Create(OpCodes.Newarr, TypeSystem.ObjectReference)); 261 | t.Add(Instruction.Create(OpCodes.Stloc, body.Variables[4])); 262 | t.Add(Instruction.Create(OpCodes.Ldloc, body.Variables[4])); 263 | 264 | t.Add(Instruction.Create(OpCodes.Ldc_I4_0)); 265 | 266 | t.Add(Instruction.Create(OpCodes.Ldloc_2)); 267 | t.Add(Instruction.Create(OpCodes.Callvirt, current)); 268 | 269 | 270 | t.Add(Instruction.Create(OpCodes.Stelem_Ref)); 271 | t.Add(Instruction.Create(OpCodes.Ldloc, body.Variables[4])); 272 | 273 | t.Add(Instruction.Create(OpCodes.Call, formatMethod)); 274 | }, 275 | _ => _.Add(Instruction.Create(OpCodes.Ldstr, "null"))); 276 | ins.Add(Instruction.Create(OpCodes.Callvirt, appendString)); 277 | ins.Add(Instruction.Create(OpCodes.Pop)); 278 | }); 279 | 280 | AppendString(ins, ListEnd); 281 | StringBuilderToString(ins); 282 | }, 283 | nf => 284 | { 285 | ins.Add(Instruction.Create(OpCodes.Pop)); 286 | ins.Add(Instruction.Create(OpCodes.Ldstr, "null")); 287 | }); 288 | } 289 | else 290 | { 291 | If(ins, 292 | c => 293 | { 294 | ins.Add(Instruction.Create(OpCodes.Dup)); 295 | AddBoxing(property, targetType, c); 296 | }, 297 | t => AddBoxing(property, targetType, t), 298 | e => 299 | { 300 | ins.Add(Instruction.Create(OpCodes.Pop)); 301 | ins.Add(Instruction.Create(OpCodes.Ldstr, "null")); 302 | }); 303 | } 304 | } 305 | 306 | ins.Add(Instruction.Create(OpCodes.Stelem_Ref)); 307 | } 308 | 309 | static void AddBoxing(PropertyDefinition property, TypeDefinition targetType, Collection ins) 310 | { 311 | if (property.PropertyType.IsValueType || property.PropertyType.IsGenericParameter) 312 | { 313 | var genericType = property.PropertyType.GetGenericInstanceType(targetType); 314 | ins.Add(Instruction.Create(OpCodes.Box, genericType)); 315 | } 316 | } 317 | 318 | void NewStringBuilder(Collection ins) 319 | { 320 | var stringBuilderConstructor = ModuleDefinition.ImportReference(typeof(StringBuilder).GetConstructor([])); 321 | ins.Add(Instruction.Create(OpCodes.Newobj, stringBuilderConstructor)); 322 | ins.Add(Instruction.Create(OpCodes.Stloc_1)); 323 | } 324 | 325 | void GetEnumerator(Collection ins) 326 | { 327 | ins.Add(Instruction.Create(OpCodes.Callvirt, getEnumerator)); 328 | ins.Add(Instruction.Create(OpCodes.Stloc_2)); 329 | } 330 | 331 | static void AssignFalseToFirstFLag(Collection ins) 332 | { 333 | ins.Add(Instruction.Create(OpCodes.Ldc_I4_0)); 334 | ins.Add(Instruction.Create(OpCodes.Stloc_3)); 335 | } 336 | 337 | static void While( 338 | Collection ins, 339 | Action> condition, 340 | Action> body) 341 | { 342 | var loopBegin = Instruction.Create(OpCodes.Nop); 343 | var loopEnd = Instruction.Create(OpCodes.Nop); 344 | 345 | ins.Add(loopBegin); 346 | 347 | condition(ins); 348 | 349 | ins.Add(Instruction.Create(OpCodes.Brfalse, loopEnd)); 350 | 351 | body(ins); 352 | 353 | ins.Add(Instruction.Create(OpCodes.Br, loopBegin)); 354 | ins.Add(loopEnd); 355 | } 356 | 357 | void AppendString(Collection ins, string str) 358 | { 359 | ins.Add(Instruction.Create(OpCodes.Ldloc_1)); 360 | ins.Add(Instruction.Create(OpCodes.Ldstr, str)); 361 | ins.Add(Instruction.Create(OpCodes.Callvirt, appendString)); 362 | ins.Add(Instruction.Create(OpCodes.Pop)); 363 | } 364 | 365 | void StringBuilderToString(Collection ins) 366 | { 367 | ins.Add(Instruction.Create(OpCodes.Ldloc_1)); 368 | var toStringMethod = ModuleDefinition.ImportReference(stringBuilderType.Resolve().FindMethod("ToString")); 369 | ins.Add(Instruction.Create(OpCodes.Callvirt, toStringMethod)); 370 | } 371 | 372 | static void If(Collection ins, 373 | Action> condition, 374 | Action> thenStatement, 375 | Action> elseStatement) 376 | { 377 | var ifEnd = Instruction.Create(OpCodes.Nop); 378 | var ifElse = Instruction.Create(OpCodes.Nop); 379 | 380 | condition(ins); 381 | 382 | ins.Add(Instruction.Create(OpCodes.Brfalse, ifElse)); 383 | 384 | thenStatement(ins); 385 | 386 | ins.Add(Instruction.Create(OpCodes.Br, ifEnd)); 387 | ins.Add(ifElse); 388 | 389 | elseStatement(ins); 390 | 391 | ins.Add(ifEnd); 392 | } 393 | 394 | void AppendSeparator(Collection ins) 395 | { 396 | If(ins, 397 | _ => _.Add(Instruction.Create(OpCodes.Ldloc_3)), 398 | t => AppendString(t, PropertiesSeparator), 399 | e => 400 | { 401 | ins.Add(Instruction.Create(OpCodes.Ldc_I4_1)); 402 | ins.Add(Instruction.Create(OpCodes.Stloc_3)); 403 | }); 404 | } 405 | 406 | 407 | string GetFormatString(TypeDefinition type, PropertyDefinition[] properties) 408 | { 409 | var builder = new StringBuilder(); 410 | var offset = 0; 411 | 412 | if (WrapWithBrackets) 413 | { 414 | builder.Append("{{"); 415 | } 416 | 417 | if (WriteTypeName) 418 | { 419 | builder.AppendFormat("T{0}\"", PropertyNameToValueSeparator); 420 | 421 | if (!type.HasGenericParameters) 422 | { 423 | builder.Append(type.Name); 424 | } 425 | else 426 | { 427 | var name = type.Name.Remove(type.Name.IndexOf('`')); 428 | offset = type.GenericParameters.Count; 429 | builder.Append(name); 430 | builder.Append('<'); 431 | for (var i = 0; i < offset; i++) 432 | { 433 | builder.Append("{"); 434 | builder.Append(i); 435 | builder.Append("}"); 436 | if (i + 1 != offset) 437 | { 438 | builder.Append(PropertiesSeparator); 439 | } 440 | } 441 | 442 | builder.Append('>'); 443 | } 444 | 445 | builder.Append("\"" + PropertiesSeparator); 446 | } 447 | 448 | for (var i = 0; i < properties.Length; i++) 449 | { 450 | var property = properties[i]; 451 | builder.Append(property.Name); 452 | builder.Append(PropertyNameToValueSeparator); 453 | 454 | if (HaveToAddQuotes(property.PropertyType)) 455 | { 456 | builder.Append('"'); 457 | } 458 | 459 | builder.Append('{'); 460 | builder.Append(i + offset); 461 | 462 | if (property.PropertyType.FullName == "System.DateTime") 463 | { 464 | builder.Append(":O"); 465 | } 466 | 467 | if (property.PropertyType.FullName == "System.TimeSpan") 468 | { 469 | builder.Append(":c"); 470 | } 471 | 472 | builder.Append("}"); 473 | 474 | if (HaveToAddQuotes(property.PropertyType)) 475 | { 476 | builder.Append('"'); 477 | } 478 | 479 | if (i != properties.Length - 1) 480 | { 481 | builder.Append(PropertiesSeparator); 482 | } 483 | } 484 | 485 | if (WrapWithBrackets) 486 | { 487 | builder.Append("}}"); 488 | } 489 | 490 | return builder.ToString(); 491 | } 492 | 493 | static bool HaveToAddQuotes(TypeReference type) 494 | { 495 | var name = type.FullName; 496 | if (name is "System.String" or "System.Char" or "System.DateTime" or "System.TimeSpan" or "System.Guid") 497 | { 498 | return true; 499 | } 500 | 501 | var resolved = type.Resolve(); 502 | return resolved is {IsEnum: true}; 503 | } 504 | 505 | public override bool ShouldCleanReference => true; 506 | 507 | static void RemoveFodyAttributes(TypeDefinition type, PropertyDefinition[] allProperties) 508 | { 509 | type.RemoveAttribute("ToStringAttribute"); 510 | foreach (var property in allProperties) 511 | { 512 | property.RemoveAttribute("IgnoreDuringToStringAttribute"); 513 | } 514 | } 515 | 516 | static IEnumerable RemoveIgnoredProperties(IEnumerable allProperties) 517 | { 518 | return allProperties 519 | .Where(_ => _.CustomAttributes.All(y => y.AttributeType.Name != "IgnoreDuringToStringAttribute")); 520 | } 521 | 522 | class PropertyNameEqualityComparer : IEqualityComparer 523 | { 524 | public bool Equals(PropertyDefinition x, PropertyDefinition y) 525 | => (x == null && y == null) || x?.Name == y?.Name; 526 | 527 | public int GetHashCode(PropertyDefinition obj) 528 | { 529 | if (obj == null) 530 | { 531 | throw new ArgumentNullException(nameof(obj)); 532 | } 533 | 534 | return obj.Name.GetHashCode(); 535 | } 536 | 537 | public static readonly PropertyNameEqualityComparer Default = new(); 538 | } 539 | 540 | string PropertyNameToValueSeparator => ReadStringValueFromConfig("PropertyNameToValueSeparator", ": "); 541 | string PropertiesSeparator => ReadStringValueFromConfig("PropertiesSeparator", ", "); 542 | string ListStart => ReadStringValueFromConfig("ListStart", "["); 543 | string ListEnd => ReadStringValueFromConfig("ListEnd", "]"); 544 | bool WrapWithBrackets => ReadBoolValueFromConfig("WrapWithBrackets", true); 545 | bool WriteTypeName => ReadBoolValueFromConfig("WriteTypeName", true); 546 | 547 | string ReadStringValueFromConfig(string nodeName, string defaultValue) 548 | { 549 | var node = Config?.Attributes().FirstOrDefault(a => a.Name.LocalName == nodeName); 550 | return node?.Value ?? defaultValue; 551 | } 552 | 553 | bool ReadBoolValueFromConfig(string nodeName, bool defaultValue) 554 | { 555 | var node = Config?.Attributes().FirstOrDefault(a => a.Name.LocalName == nodeName); 556 | return node != null && bool.TryParse(node.Value, out var nodeValue) 557 | ? nodeValue 558 | : defaultValue; 559 | } 560 | } -------------------------------------------------------------------------------- /ToString.Fody/PropertyDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | public static class PropertyDefinitionExtensions 4 | { 5 | public static MethodReference GetGetMethod(this PropertyDefinition property, TypeReference targetType) 6 | { 7 | MethodReference method = property.GetMethod; 8 | if (!method.DeclaringType.HasGenericParameters) 9 | { 10 | return method; 11 | } 12 | 13 | var genericInstanceType = property.DeclaringType.GetGenericInstanceType(targetType); 14 | return new(method.Name, method.ReturnType) 15 | { 16 | DeclaringType = genericInstanceType, 17 | HasThis = true 18 | }; 19 | } 20 | } -------------------------------------------------------------------------------- /ToString.Fody/ToString.Fody.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | portable 6 | Simon Cropp 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ToString.Fody/TypeDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Mono.Cecil; 4 | using Mono.Collections.Generic; 5 | 6 | static class TypeDefinitionExtensions 7 | { 8 | public static MethodDefinition FindMethod(this TypeDefinition typeDefinition, string method, params string[] paramTypes) 9 | { 10 | return typeDefinition.Methods.First(_ => _.Name == method && _.IsMatch(paramTypes)); 11 | } 12 | 13 | public static bool IsCollection(this TypeDefinition type) 14 | { 15 | return !type.Name.Equals("String") && 16 | type.Interfaces.Any(_ => _.InterfaceType.Name.Equals("IEnumerable")); 17 | } 18 | 19 | public static IEnumerable GetProperties(this TypeDefinition type) 20 | { 21 | var currentType = type; 22 | while (currentType.FullName != typeof(object).FullName) 23 | { 24 | foreach (var currentProperty in currentType.Properties) 25 | { 26 | yield return currentProperty; 27 | } 28 | currentType = currentType.BaseType.Resolve(); 29 | } 30 | } 31 | 32 | public static TypeReference GetGenericInstanceType(this TypeReference type, TypeReference targetType) 33 | { 34 | if (targetType is GenericInstanceType genericInstance) 35 | { 36 | return genericInstance; 37 | } 38 | 39 | if (type.IsGenericParameter) 40 | { 41 | var genericParameter = (GenericParameter)type; 42 | 43 | var current = targetType; 44 | var currentResolved = current.Resolve(); 45 | 46 | while (currentResolved.FullName != genericParameter.DeclaringType.FullName) 47 | { 48 | if (currentResolved.BaseType == null) 49 | { 50 | return type; 51 | } 52 | current = currentResolved.BaseType; 53 | currentResolved = current.Resolve(); 54 | } 55 | 56 | if (current is GenericInstanceType genericInstanceType) 57 | { 58 | return genericInstanceType.GenericArguments[genericParameter.Position]; 59 | } 60 | 61 | return type; 62 | } 63 | 64 | if (type.HasGenericParameters) 65 | { 66 | GenericInstanceType genericInstanceType; 67 | var parent = targetType; 68 | var parentReference = targetType; 69 | 70 | if (type.FullName == targetType.Resolve().FullName) 71 | { 72 | genericInstanceType = GetGenericInstanceType(type, type.GenericParameters); 73 | } 74 | else 75 | { 76 | var propertyType = type.Resolve(); 77 | 78 | TypeDefinition parentResolved; 79 | while (parent != null && propertyType.FullName != (parentResolved = parent.Resolve()).FullName) 80 | { 81 | parentReference = parentResolved.BaseType; 82 | parent = parentResolved.BaseType?.Resolve(); 83 | } 84 | 85 | genericInstanceType = parentReference as GenericInstanceType; 86 | if (genericInstanceType == null) 87 | { 88 | genericInstanceType = GetGenericInstanceType(type, parentReference.GenericParameters); 89 | } 90 | } 91 | 92 | return genericInstanceType; 93 | } 94 | 95 | return type; 96 | } 97 | 98 | static GenericInstanceType GetGenericInstanceType(TypeReference type, Collection parameters) 99 | { 100 | var genericInstanceType = new GenericInstanceType(type); 101 | foreach (var genericParameter in parameters) 102 | { 103 | genericInstanceType.GenericArguments.Add(genericParameter); 104 | } 105 | return genericInstanceType; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ToString.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.29201.188 4 | MinimumVisualStudioVersion = 16.0.29201.188 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToString.Fody", "ToString.Fody\ToString.Fody.csproj", "{C3578A7B-09A6-4444-9383-0DEAFA4958BD}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{5A86453B-96FB-4B6E-A283-225BB9F753D3}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyToProcess", "AssemblyToProcess\AssemblyToProcess.csproj", "{40939411-32F0-48DD-B17B-FA46DD5D9B25}" 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToString", "ToString\ToString.csproj", "{70804914-C3D9-4737-BCB8-B3D40F305DB3}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReferencedDependency", "ReferencedDependency\ReferencedDependency.csproj", "{34899A51-EFEA-4688-805F-0354166FCA09}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{65983802-9E62-4FBC-A145-AFE85CA85CE3}" 16 | ProjectSection(SolutionItems) = preProject 17 | Directory.Build.props = Directory.Build.props 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|x86 = Debug|x86 24 | Release|Any CPU = Release|Any CPU 25 | Release|x86 = Release|x86 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|x86.ActiveCfg = Release|Any CPU 34 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {5A86453B-96FB-4B6E-A283-225BB9F753D3}.Release|x86.ActiveCfg = Release|Any CPU 40 | {40939411-32F0-48DD-B17B-FA46DD5D9B25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {40939411-32F0-48DD-B17B-FA46DD5D9B25}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {40939411-32F0-48DD-B17B-FA46DD5D9B25}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {40939411-32F0-48DD-B17B-FA46DD5D9B25}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {40939411-32F0-48DD-B17B-FA46DD5D9B25}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {40939411-32F0-48DD-B17B-FA46DD5D9B25}.Release|x86.ActiveCfg = Release|Any CPU 46 | {70804914-C3D9-4737-BCB8-B3D40F305DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {70804914-C3D9-4737-BCB8-B3D40F305DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {70804914-C3D9-4737-BCB8-B3D40F305DB3}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {70804914-C3D9-4737-BCB8-B3D40F305DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {70804914-C3D9-4737-BCB8-B3D40F305DB3}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {70804914-C3D9-4737-BCB8-B3D40F305DB3}.Release|x86.ActiveCfg = Release|Any CPU 52 | {34899A51-EFEA-4688-805F-0354166FCA09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {34899A51-EFEA-4688-805F-0354166FCA09}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {34899A51-EFEA-4688-805F-0354166FCA09}.Debug|x86.ActiveCfg = Debug|Any CPU 55 | {34899A51-EFEA-4688-805F-0354166FCA09}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {34899A51-EFEA-4688-805F-0354166FCA09}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {34899A51-EFEA-4688-805F-0354166FCA09}.Release|x86.ActiveCfg = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(ExtensibilityGlobals) = postSolution 63 | SolutionGuid = {A79AB37F-72AC-4F16-AE02-49A080649511} 64 | EndGlobalSection 65 | EndGlobal 66 | -------------------------------------------------------------------------------- /ToString.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | True 4 | True 5 | True 6 | False 7 | 8 | True 9 | SOLUTION 10 | SUGGESTION 11 | SUGGESTION 12 | DO_NOT_SHOW 13 | ERROR 14 | DO_NOT_SHOW 15 | DO_NOT_SHOW 16 | DO_NOT_SHOW 17 | DO_NOT_SHOW 18 | WARNING 19 | ERROR 20 | ERROR 21 | ERROR 22 | ERROR 23 | ERROR 24 | DO_NOT_SHOW 25 | DO_NOT_SHOW 26 | DO_NOT_SHOW 27 | DO_NOT_SHOW 28 | ERROR 29 | ERROR 30 | ERROR 31 | ERROR 32 | ERROR 33 | ERROR 34 | ERROR 35 | ERROR 36 | ERROR 37 | ERROR 38 | ERROR 39 | ERROR 40 | ERROR 41 | ERROR 42 | ERROR 43 | ERROR 44 | ERROR 45 | ERROR 46 | ERROR 47 | ERROR 48 | ERROR 49 | DO_NOT_SHOW 50 | DO_NOT_SHOW 51 | ERROR 52 | DO_NOT_SHOW 53 | DO_NOT_SHOW 54 | ERROR 55 | ERROR 56 | ERROR 57 | ERROR 58 | ERROR 59 | ERROR 60 | ERROR 61 | ERROR 62 | ERROR 63 | WARNING 64 | ERROR 65 | ERROR 66 | ERROR 67 | ERROR 68 | ERROR 69 | ERROR 70 | ERROR 71 | SUGGESTION 72 | SUGGESTION 73 | ERROR 74 | ERROR 75 | ERROR 76 | ERROR 77 | ERROR 78 | ERROR 79 | ERROR 80 | SUGGESTION 81 | ERROR 82 | ERROR 83 | ERROR 84 | ERROR 85 | ERROR 86 | ERROR 87 | ERROR 88 | ERROR 89 | ERROR 90 | ERROR 91 | ERROR 92 | WARNING 93 | ERROR 94 | ERROR 95 | ERROR 96 | DoHide 97 | DoHide 98 | DoHide 99 | DoHide 100 | DoHide 101 | DoHide 102 | DoHide 103 | DoHide 104 | DoHide 105 | DoHide 106 | DoHide 107 | DoHide 108 | DoHide 109 | DoHide 110 | DoHide 111 | DoHide 112 | DoHide 113 | DoHide 114 | ERROR 115 | ERROR 116 | ERROR 117 | ERROR 118 | ERROR 119 | ERROR 120 | ERROR 121 | ERROR 122 | ERROR 123 | ERROR 124 | ERROR 125 | ERROR 126 | ERROR 127 | ERROR 128 | DO_NOT_SHOW 129 | SUGGESTION 130 | WARNING 131 | WARNING 132 | ERROR 133 | HINT 134 | WARNING 135 | ERROR 136 | ERROR 137 | ERROR 138 | <?xml version="1.0" encoding="utf-16"?><Profile name="Format My Code Using &quot;Particular&quot; conventions"><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssReformatCode>True</CssReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><HtmlReformatCode>True</HtmlReformatCode><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties></Profile> 139 | Default: Reformat Code 140 | Format My Code Using "Particular" conventions 141 | Implicit 142 | Implicit 143 | False 144 | False 145 | ALWAYS_ADD 146 | ALWAYS_ADD 147 | ALWAYS_ADD 148 | ALWAYS_ADD 149 | ALWAYS_ADD 150 | NEVER 151 | False 152 | False 153 | False 154 | False 155 | CHOP_ALWAYS 156 | False 157 | CHOP_ALWAYS 158 | CHOP_ALWAYS 159 | True 160 | True 161 | ZeroIndent 162 | ZeroIndent 163 | <?xml version="1.0" encoding="utf-16"?> 164 | <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> 165 | <TypePattern DisplayName="COM interfaces or structs"> 166 | <TypePattern.Match> 167 | <Or> 168 | <And> 169 | <Kind Is="Interface" /> 170 | <Or> 171 | <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> 172 | <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> 173 | </Or> 174 | </And> 175 | <Kind Is="Struct" /> 176 | </Or> 177 | </TypePattern.Match> 178 | </TypePattern> 179 | <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> 180 | <TypePattern.Match> 181 | <And> 182 | <Kind Is="Class" /> 183 | <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> 184 | <HasAttribute Name="NUnit.Framework.TestCaseFixtureAttribute" Inherited="True" /> 185 | </And> 186 | </TypePattern.Match> 187 | <Entry DisplayName="Setup/Teardown Methods"> 188 | <Entry.Match> 189 | <And> 190 | <Kind Is="Method" /> 191 | <Or> 192 | <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> 193 | <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> 194 | <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> 195 | <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> 196 | </Or> 197 | </And> 198 | </Entry.Match> 199 | </Entry> 200 | <Entry DisplayName="All other members" /> 201 | <Entry DisplayName="Test Methods" Priority="100"> 202 | <Entry.Match> 203 | <And> 204 | <Kind Is="Method" /> 205 | <HasAttribute Name="NUnit.Framework.TestAttribute" /> 206 | </And> 207 | </Entry.Match> 208 | <Entry.SortBy> 209 | <Name /> 210 | </Entry.SortBy> 211 | </Entry> 212 | </TypePattern> 213 | <TypePattern DisplayName="Default Pattern"> 214 | <Entry DisplayName="Public Delegates" Priority="100"> 215 | <Entry.Match> 216 | <And> 217 | <Access Is="Public" /> 218 | <Kind Is="Delegate" /> 219 | </And> 220 | </Entry.Match> 221 | <Entry.SortBy> 222 | <Name /> 223 | </Entry.SortBy> 224 | </Entry> 225 | <Entry DisplayName="Public Enums" Priority="100"> 226 | <Entry.Match> 227 | <And> 228 | <Access Is="Public" /> 229 | <Kind Is="Enum" /> 230 | </And> 231 | </Entry.Match> 232 | <Entry.SortBy> 233 | <Name /> 234 | </Entry.SortBy> 235 | </Entry> 236 | <Entry DisplayName="Constructors"> 237 | <Entry.Match> 238 | <Kind Is="Constructor" /> 239 | </Entry.Match> 240 | <Entry.SortBy> 241 | <Static /> 242 | </Entry.SortBy> 243 | </Entry> 244 | <Entry DisplayName="Properties, Indexers"> 245 | <Entry.Match> 246 | <Or> 247 | <Kind Is="Property" /> 248 | <Kind Is="Indexer" /> 249 | </Or> 250 | </Entry.Match> 251 | </Entry> 252 | <Entry DisplayName="Interface Implementations" Priority="100"> 253 | <Entry.Match> 254 | <And> 255 | <Kind Is="Member" /> 256 | <ImplementsInterface /> 257 | </And> 258 | </Entry.Match> 259 | <Entry.SortBy> 260 | <ImplementsInterface Immediate="True" /> 261 | </Entry.SortBy> 262 | </Entry> 263 | <Entry DisplayName="All other members" /> 264 | <Entry DisplayName="Fields"> 265 | <Entry.Match> 266 | <And> 267 | <Kind Is="Field" /> 268 | <Not> 269 | <Static /> 270 | </Not> 271 | </And> 272 | </Entry.Match> 273 | <Entry.SortBy> 274 | <Access /> 275 | <Readonly /> 276 | </Entry.SortBy> 277 | </Entry> 278 | <Entry DisplayName="Static Fields and Constants"> 279 | <Entry.Match> 280 | <Or> 281 | <Kind Is="Constant" /> 282 | <And> 283 | <Kind Is="Field" /> 284 | <Static /> 285 | </And> 286 | </Or> 287 | </Entry.Match> 288 | <Entry.SortBy> 289 | <Kind Order="Constant Field" /> 290 | </Entry.SortBy> 291 | </Entry> 292 | <Entry DisplayName="Nested Types"> 293 | <Entry.Match> 294 | <Kind Is="Type" /> 295 | </Entry.Match> 296 | </Entry> 297 | </TypePattern> 298 | </Patterns> 299 | <?xml version="1.0" encoding="utf-8" ?> 300 | 301 | <!-- 302 | I. Overall 303 | 304 | I.1 Each pattern can have <Match>....</Match> element. For the given type declaration, the pattern with the match, evaluated to 'true' with the largest weight, will be used 305 | I.2 Each pattern consists of the sequence of <Entry>...</Entry> elements. Type member declarations are distributed between entries 306 | I.3 If pattern has RemoveAllRegions="true" attribute, then all regions will be cleared prior to reordering. Otherwise, only auto-generated regions will be cleared 307 | I.4 The contents of each entry is sorted by given keys (First key is primary, next key is secondary, etc). Then the declarations are grouped and en-regioned by given property 308 | 309 | II. Available match operands 310 | 311 | Each operand may have Weight="..." attribute. This weight will be added to the match weight if the operand is evaluated to 'true'. 312 | The default weight is 1 313 | 314 | II.1 Boolean functions: 315 | II.1.1 <And>....</And> 316 | II.1.2 <Or>....</Or> 317 | II.1.3 <Not>....</Not> 318 | 319 | II.2 Operands 320 | II.2.1 <Kind Is="..."/>. Kinds are: class, struct, interface, enum, delegate, type, constructor, destructor, property, indexer, method, operator, field, constant, event, member 321 | II.2.2 <Name Is="..." [IgnoreCase="true/false"] />. The 'Is' attribute contains regular expression 322 | II.2.3 <HasAttribute CLRName="..." [Inherit="true/false"] />. The 'CLRName' attribute contains regular expression 323 | II.2.4 <Access Is="..."/>. The 'Is' values are: public, protected, internal, protected internal, private 324 | II.2.5 <Static/> 325 | II.2.6 <Abstract/> 326 | II.2.7 <Virtual/> 327 | II.2.8 <Override/> 328 | II.2.9 <Sealed/> 329 | II.2.10 <Readonly/> 330 | II.2.11 <ImplementsInterface CLRName="..."/>. The 'CLRName' attribute contains regular expression 331 | II.2.12 <HandlesEvent /> 332 | --> 333 | 334 | <Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> 335 | 336 | <!--Do not reorder COM interfaces and structs marked by StructLayout attribute--> 337 | <Pattern> 338 | <Match> 339 | <Or Weight="100"> 340 | <And> 341 | <Kind Is="interface"/> 342 | <Or> 343 | <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> 344 | <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> 345 | </Or> 346 | </And> 347 | <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> 348 | </Or> 349 | </Match> 350 | </Pattern> 351 | 352 | <!--Special formatting of NUnit test fixture--> 353 | <Pattern RemoveAllRegions="true"> 354 | <Match> 355 | <And Weight="100"> 356 | <Kind Is="class"/> 357 | <HasAttribute CLRName="NUnit.Framework.TestFixtureAttribute" Inherit="true"/> 358 | </And> 359 | </Match> 360 | 361 | <!--Setup/Teardow--> 362 | <Entry> 363 | <Match> 364 | <And> 365 | <Kind Is="method"/> 366 | <Or> 367 | <HasAttribute CLRName="NUnit.Framework.SetUpAttribute" Inherit="true"/> 368 | <HasAttribute CLRName="NUnit.Framework.TearDownAttribute" Inherit="true"/> 369 | <HasAttribute CLRName="NUnit.Framework.FixtureSetUpAttribute" Inherit="true"/> 370 | <HasAttribute CLRName="NUnit.Framework.FixtureTearDownAttribute" Inherit="true"/> 371 | </Or> 372 | </And> 373 | </Match> 374 | </Entry> 375 | 376 | <!--All other members--> 377 | <Entry/> 378 | 379 | <!--Test methods--> 380 | <Entry> 381 | <Match> 382 | <And Weight="100"> 383 | <Kind Is="method"/> 384 | <HasAttribute CLRName="NUnit.Framework.TestAttribute" Inherit="false"/> 385 | </And> 386 | </Match> 387 | <Sort> 388 | <Name/> 389 | </Sort> 390 | </Entry> 391 | </Pattern> 392 | 393 | <!--Default pattern--> 394 | <Pattern> 395 | 396 | <!--public delegate--> 397 | <Entry> 398 | <Match> 399 | <And Weight="100"> 400 | <Access Is="public"/> 401 | <Kind Is="delegate"/> 402 | </And> 403 | </Match> 404 | <Sort> 405 | <Name/> 406 | </Sort> 407 | </Entry> 408 | 409 | <!--public enum--> 410 | <Entry> 411 | <Match> 412 | <And Weight="100"> 413 | <Access Is="public"/> 414 | <Kind Is="enum"/> 415 | </And> 416 | </Match> 417 | <Sort> 418 | <Name/> 419 | </Sort> 420 | </Entry> 421 | 422 | <!--Constructors. Place static one first--> 423 | <Entry> 424 | <Match> 425 | <Kind Is="constructor"/> 426 | </Match> 427 | <Sort> 428 | <Static/> 429 | </Sort> 430 | </Entry> 431 | 432 | <!--properties, indexers--> 433 | <Entry> 434 | <Match> 435 | <Or> 436 | <Kind Is="property"/> 437 | <Kind Is="indexer"/> 438 | </Or> 439 | </Match> 440 | </Entry> 441 | 442 | <!--interface implementations--> 443 | <Entry> 444 | <Match> 445 | <And Weight="100"> 446 | <Kind Is="member"/> 447 | <ImplementsInterface/> 448 | </And> 449 | </Match> 450 | <Sort> 451 | <ImplementsInterface Immediate="true"/> 452 | </Sort> 453 | </Entry> 454 | 455 | <!--all other members--> 456 | <Entry/> 457 | 458 | <!--static fields and constants--> 459 | <Entry> 460 | <Match> 461 | <Or> 462 | <Kind Is="constant"/> 463 | <And> 464 | <Kind Is="field"/> 465 | <Static/> 466 | </And> 467 | </Or> 468 | </Match> 469 | <Sort> 470 | <Kind Order="constant field"/> 471 | </Sort> 472 | </Entry> 473 | 474 | <!--instance fields--> 475 | <Entry> 476 | <Match> 477 | <And> 478 | <Kind Is="field"/> 479 | <Not> 480 | <Static/> 481 | </Not> 482 | </And> 483 | </Match> 484 | <Sort> 485 | <Readonly/> 486 | <Name/> 487 | </Sort> 488 | </Entry> 489 | 490 | <!--nested types--> 491 | <Entry> 492 | <Match> 493 | <Kind Is="type"/> 494 | </Match> 495 | <Sort> 496 | <Name/> 497 | </Sort> 498 | </Entry> 499 | </Pattern> 500 | 501 | </Patterns> 502 | 503 | CustomLayout 504 | True 505 | False 506 | True 507 | False 508 | True 509 | False 510 | False 511 | False 512 | True 513 | Automatic property 514 | True 515 | False 516 | False 517 | DB 518 | DTC 519 | ID 520 | NSB 521 | SLA 522 | $object$_On$event$ 523 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 524 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 525 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 526 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 527 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 528 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 529 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 530 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 531 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 532 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 533 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 534 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 535 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 536 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 537 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 538 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 539 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 540 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 541 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"><ElementKinds><Kind Name="TYPE_PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></Policy> 542 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 543 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 544 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local variables"><ElementKinds><Kind Name="LOCAL_VARIABLE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 545 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 546 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 547 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 548 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"><ElementKinds><Kind Name="ENUM_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 549 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 550 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 551 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Interfaces"><ElementKinds><Kind Name="INTERFACE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></Policy> 552 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 553 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 554 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 555 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 556 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 557 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 558 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 559 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 560 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 561 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 562 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 563 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 564 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 565 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 566 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 567 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 568 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 569 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 570 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 571 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 572 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 573 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 574 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 575 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 576 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 577 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 578 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 579 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 580 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 581 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 582 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 583 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 584 | $object$_On$event$ 585 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 586 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 587 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 588 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 589 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 590 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 591 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 592 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 593 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 594 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 595 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 596 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 597 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 598 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 599 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 600 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 601 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 602 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 603 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 604 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 605 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 606 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 607 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 608 | True 609 | True 610 | True 611 | True 612 | True 613 | True 614 | True 615 | True 616 | True 617 | True 618 | True 619 | True 620 | True 621 | True 622 | True 623 | True 624 | True 625 | 626 | 627 | 628 | <data /> 629 | <data><IncludeFilters /><ExcludeFilters /></data> -------------------------------------------------------------------------------- /ToString/IgnoreDuringToStringAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /// 4 | /// Property will be ignored during generating ToString method. 5 | /// 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public sealed class IgnoreDuringToStringAttribute : Attribute; -------------------------------------------------------------------------------- /ToString/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fody/ToString/67b90600dbdcaa3acc7dbc4222fbed11d4f27087/ToString/Key.snk -------------------------------------------------------------------------------- /ToString/ToString.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452;netstandard2 5 | true 6 | Key.snk 7 | true 8 | Generate ToString method from public properties. 9 | ToString, Formatting, ILWeaving, Fody, Cecil 10 | $(SolutionDir)nugets 11 | https://raw.githubusercontent.com/Fody/ToString/master/package_icon.png 12 | https://github.com/Fody/ToString 13 | MIT 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ToString/ToStringAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /// 4 | /// Adds ToString method to class. 5 | /// 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] 7 | public sealed class ToStringAttribute : Attribute; -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | environment: 3 | DOTNET_NOLOGO: true 4 | DOTNET_CLI_TELEMETRY_OPTOUT: true 5 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 6 | build_script: 7 | - pwsh: | 8 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" 9 | ./dotnet-install.ps1 -JSonFile global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet' 10 | - dotnet build --configuration Release 11 | - dotnet test --configuration Release --no-build --no-restore 12 | test: off 13 | artifacts: 14 | - path: nugets\**\*.nupkg -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.101", 4 | "allowPrerelease": true, 5 | "rollForward": "latestFeature" 6 | } 7 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fody/ToString/67b90600dbdcaa3acc7dbc4222fbed11d4f27087/license.txt -------------------------------------------------------------------------------- /package_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fody/ToString/67b90600dbdcaa3acc7dbc4222fbed11d4f27087/package_icon.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ToString.Fody 2 | 3 | [![NuGet Status](https://img.shields.io/nuget/v/ToString.Fody.svg)](https://www.nuget.org/packages/ToString.Fody/) 4 | 5 | Generates ToString method from public properties for class decorated with a `[ToString]` Attribute. 6 | 7 | **See [Milestones](../../milestones?state=closed) for release notes.** 8 | 9 | 10 | ### This is an add-in for [Fody](https://github.com/Fody/Home/) 11 | 12 | **It is expected that all developers using Fody [become a Patron on OpenCollective](https://opencollective.com/fody/contribute/patron-3059). [See Licensing/Patron FAQ](https://github.com/Fody/Home/blob/master/pages/licensing-patron-faq.md) for more information.** 13 | 14 | 15 | ## Usage 16 | 17 | See also [Fody usage](https://github.com/Fody/Home/blob/master/pages/usage.md). 18 | 19 | 20 | ### NuGet installation 21 | 22 | Install the [ToString.Fody NuGet package](https://nuget.org/packages/ToString.Fody/) and update the [Fody NuGet package](https://nuget.org/packages/Fody/): 23 | 24 | ```powershell 25 | PM> Install-Package Fody 26 | PM> Install-Package ToString.Fody 27 | ``` 28 | 29 | The `Install-Package Fody` is required since NuGet always defaults to the oldest, and most buggy, version of any dependency. 30 | 31 | 32 | ### Add to FodyWeavers.xml 33 | 34 | Add `` to [FodyWeavers.xml](https://github.com/Fody/Home/blob/master/pages/usage.md#add-fodyweaversxml) 35 | 36 | ```xml 37 | 38 | 39 | 40 | ``` 41 | 42 | 43 | ## Your Code 44 | 45 | ```csharp 46 | [ToString] 47 | class TestClass 48 | { 49 | public int Foo { get; set; } 50 | 51 | public double Bar { get; set; } 52 | 53 | [IgnoreDuringToString] 54 | public string Baz { get; set; } 55 | } 56 | ``` 57 | 58 | 59 | ## What gets compiled 60 | 61 | ```csharp 62 | class TestClass 63 | { 64 | public int Foo { get; set; } 65 | 66 | public double Bar { get; set; } 67 | 68 | public string Baz { get; set; } 69 | 70 | public override string ToString() 71 | { 72 | return string.Format( 73 | CultureInfo.InvariantCulture, 74 | "{{T: TestClass, Foo: {0}, Bar: {1}}}", 75 | Foo, 76 | Bar); 77 | } 78 | } 79 | ``` 80 | 81 | 82 | ## Options 83 | 84 | 85 | ### PropertyNameToValueSeparator 86 | 87 | Default: `: ` 88 | 89 | For example: 90 | 91 | ```xml 92 | 93 | 94 | 95 | ``` 96 | 97 | 98 | ### PropertiesSeparator 99 | 100 | Default: `, ` 101 | 102 | For example: 103 | 104 | ```xml 105 | 106 | 107 | 108 | ``` 109 | 110 | 111 | ### WrapWithBrackets 112 | 113 | Default: `true` 114 | 115 | For example: 116 | 117 | ```xml 118 | 119 | 120 | 121 | ``` 122 | 123 | 124 | ### WriteTypeName 125 | 126 | Default: `true` 127 | 128 | For example: 129 | 130 | ```xml 131 | 132 | 133 | 134 | ``` 135 | 136 | 137 | ### ListStart 138 | 139 | Default: `[` 140 | 141 | For example: 142 | 143 | ```xml 144 | 145 | 146 | 147 | ``` 148 | 149 | 150 | ### ListEnd 151 | 152 | Default: `]` 153 | 154 | For example: 155 | 156 | ```xml 157 | 158 | 159 | 160 | ``` 161 | 162 | 163 | ## Icon 164 | 165 | Icon courtesy of [The Noun Project](https://thenounproject.com) 166 | --------------------------------------------------------------------------------