Remove the dependency on System.ValueTuple (#63)

* Add an option to get tuple data via reflection.

* Removed non-relfection-based way of getting information about the tuples.

* Make methods static back.

* Remove the nuget dependency to System.ValueTuple
This commit is contained in:
Sergey Teplyakov 2018-02-23 03:24:41 -08:00 committed by Ben Adams
parent 7381924470
commit 125e373b45
7 changed files with 78 additions and 21 deletions

View File

@ -25,9 +25,6 @@
<PackageReference Include="System.Reflection.Metadata"> <PackageReference Include="System.Reflection.Metadata">
<Version>1.5.0</Version> <Version>1.5.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.ValueTuple">
<Version>4.4.0</Version>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -508,7 +508,8 @@ namespace System.Diagnostics
{ {
return "out"; return "out";
} }
else if (parameterType != null && parameterType.IsByRef)
if (parameterType != null && parameterType.IsByRef)
{ {
var attribs = parameter.GetCustomAttributes(inherit: false); var attribs = parameter.GetCustomAttributes(inherit: false);
if (attribs?.Length > 0) if (attribs?.Length > 0)
@ -549,15 +550,13 @@ namespace System.Diagnostics
{ {
var customAttribs = parameter.GetCustomAttributes(inherit: false); var customAttribs = parameter.GetCustomAttributes(inherit: false);
if ((customAttribs?.Length ?? 0) > 0) var tupleNameAttribute = customAttribs.OfType<Attribute>().FirstOrDefault(a => a.IsTupleElementNameAttribue());
{
var tupleNames = customAttribs
.OfType<TupleElementNamesAttribute>().FirstOrDefault()?.TransformNames;
if (tupleNames?.Count > 0) var tupleNames = tupleNameAttribute?.GetTransformerNames();
{
return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); if (tupleNames?.Count > 0)
} {
return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType);
} }
} }

View File

@ -1,8 +1,9 @@
// Copyright (c) Ben A Adams. All rights reserved. // 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. // 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;
using System.Reflection.Emit; using System.Threading;
namespace System.Diagnostics.Internal namespace System.Diagnostics.Internal
{ {
@ -11,6 +12,8 @@ namespace System.Diagnostics.Internal
/// </summary> /// </summary>
public static class ReflectionHelper public static class ReflectionHelper
{ {
private static PropertyInfo tranformerNamesLazyPropertyInfo;
/// <summary> /// <summary>
/// Returns true if <paramref name="type"/> is <code>System.Runtime.CompilerServices.IsReadOnlyAttribute</code>. /// Returns true if <paramref name="type"/> is <code>System.Runtime.CompilerServices.IsReadOnlyAttribute</code>.
/// </summary> /// </summary>
@ -26,5 +29,40 @@ namespace System.Diagnostics.Internal
{ {
return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
} }
/// <summary>
/// Returns true if the given <paramref name="attribute"/> is of type <code>TupleElementNameAttribute</code>.
/// </summary>
/// <remarks>
/// To avoid compile-time depencency hell with System.ValueTuple, this method uses reflection and not checks statically that
/// the given <paramref name="attribute"/> is <code>TupleElementNameAttribute</code>.
/// </remarks>
public static bool IsTupleElementNameAttribue(this Attribute attribute)
{
var attributeType = attribute.GetType();
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
attributeType.Name == "TupleElementNamesAttribute";
}
/// <summary>
/// Returns 'TransformNames' property value from a given <paramref name="attribute"/>.
/// </summary>
/// <remarks>
/// To avoid compile-time depencency hell with System.ValueTuple, this method uses reflection
/// instead of casting the attribute to a specific type.
/// </remarks>
public static IList<string> GetTransformerNames(this Attribute attribute)
{
Debug.Assert(attribute.IsTupleElementNameAttribue());
var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType());
return (IList<string>)propertyInfo.GetValue(attribute);
}
private static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType)
{
return LazyInitializer.EnsureInitialized(ref tranformerNamesLazyPropertyInfo,
() => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public));
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Jobs; using BenchmarkDotNet.Attributes.Jobs;
@ -14,5 +15,26 @@ namespace Ben.Demystifier.Benchmarks
[Benchmark(Description = "Demystify().ToString()")] [Benchmark(Description = "Demystify().ToString()")]
public string Demystify() => new Exception().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();
} }
} }

View File

@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup> </ItemGroup>

View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using Xunit; using Xunit;
namespace Ben.Demystifier.Test namespace Ben.Demystifier.Test

View File

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -22,7 +20,7 @@ namespace Ben.Demystifier.Test
{ {
try try
{ {
SimpleMethodThatThrows().Wait(); SimpleMethodThatThrows(null).Wait();
} }
catch (Exception e) catch (Exception e)
{ {
@ -39,9 +37,14 @@ namespace Ben.Demystifier.Test
Assert.Equal(original, afterDemystified); 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();
} }
} }
} }