diff --git a/src/Ben.Demystifier/Ben.Demystifier.csproj b/src/Ben.Demystifier/Ben.Demystifier.csproj index c1cd196..d51ad47 100644 --- a/src/Ben.Demystifier/Ben.Demystifier.csproj +++ b/src/Ben.Demystifier/Ben.Demystifier.csproj @@ -25,9 +25,6 @@ 1.5.0 - - 4.4.0 - diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index d2acf07..f83b54b 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -508,7 +508,8 @@ namespace System.Diagnostics { return "out"; } - else if (parameterType != null && parameterType.IsByRef) + + if (parameterType != null && parameterType.IsByRef) { var attribs = parameter.GetCustomAttributes(inherit: false); if (attribs?.Length > 0) @@ -549,15 +550,13 @@ namespace System.Diagnostics { var customAttribs = parameter.GetCustomAttributes(inherit: false); - if ((customAttribs?.Length ?? 0) > 0) - { - var tupleNames = customAttribs - .OfType().FirstOrDefault()?.TransformNames; + var tupleNameAttribute = customAttribs.OfType().FirstOrDefault(a => a.IsTupleElementNameAttribue()); - if (tupleNames?.Count > 0) - { - return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); - } + var tupleNames = tupleNameAttribute?.GetTransformerNames(); + + if (tupleNames?.Count > 0) + { + return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); } } diff --git a/src/Ben.Demystifier/Internal/ReflectionHelper.cs b/src/Ben.Demystifier/Internal/ReflectionHelper.cs index 9306532..7280b05 100644 --- a/src/Ben.Demystifier/Internal/ReflectionHelper.cs +++ b/src/Ben.Demystifier/Internal/ReflectionHelper.cs @@ -1,8 +1,9 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Reflection; -using System.Reflection.Emit; +using System.Threading; namespace System.Diagnostics.Internal { @@ -11,6 +12,8 @@ namespace System.Diagnostics.Internal /// public static class ReflectionHelper { + private static PropertyInfo tranformerNamesLazyPropertyInfo; + /// /// Returns true if is System.Runtime.CompilerServices.IsReadOnlyAttribute. /// @@ -26,5 +29,40 @@ namespace System.Diagnostics.Internal { return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); } + + /// + /// Returns true if the given is of type TupleElementNameAttribute. + /// + /// + /// To avoid compile-time depencency hell with System.ValueTuple, this method uses reflection and not checks statically that + /// the given is TupleElementNameAttribute. + /// + public static bool IsTupleElementNameAttribue(this Attribute attribute) + { + var attributeType = attribute.GetType(); + return attributeType.Namespace == "System.Runtime.CompilerServices" && + attributeType.Name == "TupleElementNamesAttribute"; + } + + /// + /// Returns 'TransformNames' property value from a given . + /// + /// + /// To avoid compile-time depencency hell with System.ValueTuple, this method uses reflection + /// instead of casting the attribute to a specific type. + /// + public static IList GetTransformerNames(this Attribute attribute) + { + Debug.Assert(attribute.IsTupleElementNameAttribue()); + + var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); + return (IList)propertyInfo.GetValue(attribute); + } + + private static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType) + { + return LazyInitializer.EnsureInitialized(ref tranformerNamesLazyPropertyInfo, + () => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)); + } } } diff --git a/test/Ben.Demystifier.Benchmarks/Exceptions.cs b/test/Ben.Demystifier.Benchmarks/Exceptions.cs index 0bc8881..12417a9 100644 --- a/test/Ben.Demystifier.Benchmarks/Exceptions.cs +++ b/test/Ben.Demystifier.Benchmarks/Exceptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes.Jobs; @@ -14,5 +15,26 @@ namespace Ben.Demystifier.Benchmarks [Benchmark(Description = "Demystify().ToString()")] public string Demystify() => new Exception().Demystify().ToString(); + + [Benchmark(Description = "(left, right).ToString()")] + public string ToStringForTupleBased() => GetException(() => ReturnsTuple()).ToString(); + + [Benchmark(Description = "(left, right).Demystify().ToString()")] + public string ToDemystifyForTupleBased() => GetException(() => ReturnsTuple()).Demystify().ToString(); + + private static Exception GetException(Action action) + { + try + { + action(); + throw new InvalidOperationException("Should not be reachable."); + } + catch (Exception e) + { + return e; + } + } + + private static List<(int left, int right)> ReturnsTuple() => throw new Exception(); } } diff --git a/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj b/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj index 9400fa6..e75a1bd 100644 --- a/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj +++ b/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj @@ -7,6 +7,7 @@ + diff --git a/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs b/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs index c00acc9..a045af2 100644 --- a/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs +++ b/test/Ben.Demystifier.Test/EnhancedStackTraceTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; using Xunit; namespace Ben.Demystifier.Test diff --git a/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs index 47ff7e2..16a2ee4 100644 --- a/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs +++ b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -22,7 +20,7 @@ namespace Ben.Demystifier.Test { try { - SimpleMethodThatThrows().Wait(); + SimpleMethodThatThrows(null).Wait(); } catch (Exception e) { @@ -39,9 +37,14 @@ namespace Ben.Demystifier.Test Assert.Equal(original, afterDemystified); } - async Task SimpleMethodThatThrows() + async Task SimpleMethodThatThrows(string value) { - throw new InvalidOperationException("message"); + if (value == null) + { + throw new InvalidOperationException("message"); + } + + await Task.Yield(); } } }