Skip to content

Commit 31864be

Browse files
Added more extension
1 parent 18b1467 commit 31864be

File tree

6 files changed

+193
-8
lines changed

6 files changed

+193
-8
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
types: [opened, synchronize, reopened]
88

99
env:
10-
DOTNET_VERSION: '7.0.x' # The .NET SDK version to use
10+
DOTNET_VERSION: '8.0.x' # The .NET SDK version to use
1111

1212
jobs:
1313
build-and-test:

.github/workflows/package.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Setup .NET Core
1515
uses: actions/setup-dotnet@v1
1616
with:
17-
dotnet-version: 7.0.x
17+
dotnet-version: 8.0.x
1818
- name: Install dependencies
1919
run: dotnet restore
2020
working-directory: src

src/Reflector/CastExtensions.cs

+112-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Reflection;
1+
using System.Globalization;
2+
using System.Reflection;
23

34
namespace Reflector;
45

@@ -8,6 +9,95 @@ public static class CastExtensions
89
private const string ImplicitCastMethodName = "op_Implicit";
910
private const string ExplicitCastMethodName = "op_Explicit";
1011

12+
public static T? SafeCast<T>(this object obj) where T : class
13+
{
14+
return obj as T;
15+
}
16+
17+
public static T CastOrDefault<T>(this object obj, T defaultValue = default)
18+
{
19+
try
20+
{
21+
return (T)obj;
22+
}
23+
catch
24+
{
25+
return defaultValue;
26+
}
27+
}
28+
29+
public static bool TryCast<T>(this object obj, out T? result) where T : class
30+
{
31+
if (obj == null)
32+
{
33+
result = default;
34+
return false;
35+
}
36+
37+
if (obj is T castedObj)
38+
{
39+
result = castedObj;
40+
return true;
41+
}
42+
43+
if (obj.CanExplicitCast<T>())
44+
{
45+
result = obj.ExplicitCast<T>();
46+
return true;
47+
}
48+
49+
result = default;
50+
return false;
51+
}
52+
53+
54+
public static Func<T, T, bool> GetComparer<T>()
55+
{
56+
if (typeof(T).IsValueType)
57+
{
58+
// Avoid causing any boxing for value types
59+
return (actual, expected) => EqualityComparer<T>.Default.Equals(actual, expected);
60+
}
61+
62+
if (typeof(T) != typeof(object))
63+
{
64+
// CompareNumerics is only relevant for numerics boxed in an object.
65+
return (actual, expected) => actual is null
66+
? expected is null
67+
: expected is not null && EqualityComparer<T>.Default.Equals(actual, expected);
68+
}
69+
70+
return (actual, expected) => actual is null
71+
? expected is null
72+
: expected is not null
73+
&& (EqualityComparer<T>.Default.Equals(actual, expected) || CompareNumerics(actual, expected));
74+
}
75+
76+
public static bool IsCastableTo<T>(this object obj)
77+
{
78+
return obj is T || obj.GetType().CanCast<T>();
79+
}
80+
81+
public static bool CheckCastCompatibility(this Type sourceType, Type targetType)
82+
{
83+
return sourceType.IsAssignableFrom(targetType) || targetType.IsAssignableFrom(sourceType);
84+
}
85+
86+
public static bool CanConvert(object source, object target, Type sourceType, Type targetType)
87+
{
88+
try
89+
{
90+
var converted = source.ConvertTo(targetType);
91+
92+
return source.Equals(converted.ConvertTo(sourceType))
93+
&& converted.Equals(target);
94+
}
95+
catch
96+
{
97+
// Ignored
98+
return false;
99+
}
100+
}
11101
public static bool CanCast<T>(this Type baseType)
12102
{
13103
return baseType.CanImplicitCast<T>() || baseType.CanExplicitCast<T>();
@@ -65,7 +155,7 @@ private static bool CanCast<T>(this Type baseType, string castMethodName)
65155
.Where(mi => mi.Name == castMethodName && mi.ReturnType == targetType)
66156
.Any(mi =>
67157
{
68-
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
158+
ParameterInfo? pi = mi.GetParameters().FirstOrDefault();
69159
return pi != null && pi.ParameterType == baseType;
70160
});
71161
}
@@ -87,13 +177,31 @@ private static T Cast<T>(this object obj, string castMethodName)
87177
.Where(mi => mi.Name == castMethodName && mi.ReturnType == typeof(T))
88178
.SingleOrDefault(mi =>
89179
{
90-
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
180+
ParameterInfo? pi = mi.GetParameters().FirstOrDefault();
91181
return pi != null && pi.ParameterType == objType;
92182
});
93183
if (conversionMethod != null)
94-
return (T)conversionMethod.Invoke(null, new[] { obj });
184+
return (T)conversionMethod.Invoke(null, new[] { obj })!;
95185
else
96186
throw new InvalidCastException($"No method to cast {objType.FullName} to {typeof(T).FullName}");
97187
}
188+
189+
private static bool CompareNumerics(object actual, object expected)
190+
{
191+
Type expectedType = expected.GetType();
192+
Type actualType = actual.GetType();
193+
194+
return actualType != expectedType
195+
&& actual.IsNumericType()
196+
&& expected.IsNumericType()
197+
&& CanConvert(actual, expected, actualType, expectedType)
198+
&& CanConvert(expected, actual, expectedType, actualType);
199+
}
200+
201+
202+
private static object ConvertTo(this object source, Type targetType)
203+
{
204+
return Convert.ChangeType(source, targetType, CultureInfo.InvariantCulture);
205+
}
98206
}
99207

src/Reflector/ReflectionExtensions.cs

+15
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,21 @@ public static bool IsPrimitive(this Type type)
105105
|| Convert.GetTypeCode(type) != TypeCode.Object);
106106
}
107107

108+
public static bool IsNumericType(this object obj)
109+
{
110+
return obj is (
111+
int or
112+
long or
113+
float or
114+
double or
115+
decimal or
116+
sbyte or
117+
byte or
118+
short or
119+
ushort or
120+
uint or
121+
ulong);
122+
}
108123
public static object? InvokeStaticMethod(this Type type, string name, params object[] args)
109124
{
110125
return type?.GetTypeInfo().InvokeMember(
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace VReflector;
2+
3+
public static class SpanReflectionExtensions
4+
{
5+
6+
/// <summary>
7+
/// Converts a ReadOnlySpan<char> to a string using reflection.
8+
/// Useful when dealing with Spans in scenarios where a string is needed.
9+
/// </summary>
10+
/// <param name="span">The ReadOnlySpan to convert.</param>
11+
/// <returns>A string representation of the span.</returns>
12+
public static string ToStringViaReflector(ReadOnlySpan<char> span)
13+
{
14+
// Use the ReadOnlySpan<char> constructor of string if available
15+
return new string(span);
16+
}
17+
18+
/// <summary>
19+
/// Compares two spans character by character for equality.
20+
/// </summary>
21+
/// <param name="span1">The first span.</param>
22+
/// <param name="span2">The second span.</param>
23+
/// <returns>True if the spans are equal; otherwise, false.</returns>
24+
public static bool AreSpansEqual(ReadOnlySpan<char> span1, ReadOnlySpan<char> span2)
25+
{
26+
return span1.SequenceEqual(span2);
27+
}
28+
29+
/// <summary>
30+
/// Extracts a substring from a ReadOnlySpan<char>.
31+
/// </summary>
32+
/// <param name="span">The span to extract from.</param>
33+
/// <param name="start">The start index.</param>
34+
/// <param name="length">The length of the substring.</param>
35+
/// <returns>A string representation of the extracted substring.</returns>
36+
public static string Substring(ReadOnlySpan<char> span, int start, int length)
37+
{
38+
return new string(span.Slice(start, length));
39+
}
40+
41+
/// <summary>
42+
/// Reflectively analyzes the contents of a span for debugging or logging.
43+
/// </summary>
44+
/// <param name="span">The span to analyze.</param>
45+
/// <returns>A string representing the span's contents.</returns>
46+
public static string Analyze(ReadOnlySpan<char> span)
47+
{
48+
return $"Span Length: {span.Length}, Contents: '{new string(span)}'";
49+
}
50+
51+
/// <summary>
52+
/// Converts any Span<T> to a human-readable format for debugging.
53+
/// </summary>
54+
/// <typeparam name="T">The type of the span elements.</typeparam>
55+
/// <param name="span">The span to convert.</param>
56+
/// <returns>A string representation of the span's contents.</returns>
57+
public static string ToDebugString<T>(ReadOnlySpan<T> span)
58+
{
59+
return $"[ {string.Join(", ", span.ToArray())} ]";
60+
}
61+
62+
}

src/Reflector/VReflector.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
4+
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
7-
<VersionPrefix>1.4.0</VersionPrefix>
7+
<VersionPrefix>1.5.0</VersionPrefix>
88
<SignAssembly>True</SignAssembly>
99
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
1010
<Title>VReflector</Title>

0 commit comments

Comments
 (0)