Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: dennisdoomen/reflectify
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.5.0
Choose a base ref
...
head repository: dennisdoomen/reflectify
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 1.5.1
Choose a head ref
  • 8 commits
  • 6 files changed
  • 2 contributors

Commits on Jan 20, 2025

  1. Bump Nuke.Common and Nuke.Components

    Bumps [Nuke.Common](https://github.com/nuke-build/nuke) and [Nuke.Components](https://github.com/nuke-build/nuke). These dependencies needed to be updated together.
    
    Updates `Nuke.Common` from 9.0.3 to 9.0.4
    - [Release notes](https://github.com/nuke-build/nuke/releases)
    - [Changelog](https://github.com/nuke-build/nuke/blob/develop/CHANGELOG.md)
    - [Commits](nuke-build/nuke@9.0.3...9.0.4)
    
    Updates `Nuke.Components` from 9.0.3 to 9.0.4
    - [Release notes](https://github.com/nuke-build/nuke/releases)
    - [Changelog](https://github.com/nuke-build/nuke/blob/develop/CHANGELOG.md)
    - [Commits](nuke-build/nuke@9.0.3...9.0.4)
    
    ---
    updated-dependencies:
    - dependency-name: Nuke.Common
      dependency-type: direct:production
      update-type: version-update:semver-patch
    - dependency-name: Nuke.Components
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and dennisdoomen committed Jan 20, 2025
    Copy the full SHA
    e64e515 View commit details
  2. Bump the dependencies

    dennisdoomen committed Jan 20, 2025
    Copy the full SHA
    6e56271 View commit details

Commits on Feb 3, 2025

  1. Bump Roslynator.Analyzers from 4.12.10 to 4.12.11

    Bumps [Roslynator.Analyzers](https://github.com/dotnet/roslynator) from 4.12.10 to 4.12.11.
    - [Release notes](https://github.com/dotnet/roslynator/releases)
    - [Changelog](https://github.com/dotnet/roslynator/blob/main/ChangeLog.md)
    - [Commits](dotnet/roslynator@v4.12.10...v4.12.11)
    
    ---
    updated-dependencies:
    - dependency-name: Roslynator.Analyzers
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and dennisdoomen committed Feb 3, 2025
    Copy the full SHA
    c5d309a View commit details

Commits on Feb 10, 2025

  1. Bump Meziantou.Analyzer and Microsoft.NETFramework.ReferenceAssemblies

    Bumps [Meziantou.Analyzer](https://github.com/meziantou/Meziantou.Analyzer) and [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet). These dependencies needed to be updated together.
    
    Updates `Meziantou.Analyzer` from 2.0.186 to 2.0.187
    - [Release notes](https://github.com/meziantou/Meziantou.Analyzer/releases)
    - [Commits](meziantou/Meziantou.Analyzer@2.0.186...2.0.187)
    
    Updates `Microsoft.NETFramework.ReferenceAssemblies` from 1.0.3 to 1.0.3
    - [Commits](https://github.com/Microsoft/dotnet/commits)
    
    ---
    updated-dependencies:
    - dependency-name: Meziantou.Analyzer
      dependency-type: direct:production
      update-type: version-update:semver-patch
    - dependency-name: Microsoft.NETFramework.ReferenceAssemblies
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and dennisdoomen committed Feb 10, 2025
    Copy the full SHA
    6aa87e1 View commit details
  2. Bump Microsoft.NETFramework.ReferenceAssemblies and Roslynator.Analyzers

    Bumps [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet) and [Roslynator.Analyzers](https://github.com/dotnet/roslynator). These dependencies needed to be updated together.
    
    Updates `Microsoft.NETFramework.ReferenceAssemblies` from 1.0.3 to 1.0.3
    - [Commits](https://github.com/Microsoft/dotnet/commits)
    
    Updates `Roslynator.Analyzers` from 4.12.11 to 4.13.0
    - [Release notes](https://github.com/dotnet/roslynator/releases)
    - [Changelog](https://github.com/dotnet/roslynator/blob/main/ChangeLog.md)
    - [Commits](dotnet/roslynator@v4.12.11...v4.13.0)
    
    ---
    updated-dependencies:
    - dependency-name: Microsoft.NETFramework.ReferenceAssemblies
      dependency-type: direct:production
      update-type: version-update:semver-patch
    - dependency-name: Roslynator.Analyzers
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and dennisdoomen committed Feb 10, 2025
    Copy the full SHA
    9420e7c View commit details

Commits on Feb 24, 2025

  1. Bump Microsoft.NETFramework.ReferenceAssemblies and Roslynator.Analyzers

    Bumps [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet) and [Roslynator.Analyzers](https://github.com/dotnet/roslynator). These dependencies needed to be updated together.
    
    Updates `Microsoft.NETFramework.ReferenceAssemblies` from 1.0.3 to 1.0.3
    - [Commits](https://github.com/Microsoft/dotnet/commits)
    
    Updates `Roslynator.Analyzers` from 4.13.0 to 4.13.1
    - [Release notes](https://github.com/dotnet/roslynator/releases)
    - [Changelog](https://github.com/dotnet/roslynator/blob/main/ChangeLog.md)
    - [Commits](dotnet/roslynator@v4.13.0...v4.13.1)
    
    ---
    updated-dependencies:
    - dependency-name: Microsoft.NETFramework.ReferenceAssemblies
      dependency-type: direct:production
      update-type: version-update:semver-patch
    - dependency-name: Roslynator.Analyzers
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored and dennisdoomen committed Feb 24, 2025
    Copy the full SHA
    874c050 View commit details

Commits on Mar 8, 2025

  1. Bump dependencies

    dennisdoomen committed Mar 8, 2025
    Copy the full SHA
    0440dcd View commit details

Commits on Mar 9, 2025

  1. Normal properties of a base-class should have precedence over explici…

    …tly implemented properties on the derived class.
    dennisdoomen committed Mar 9, 2025
    Copy the full SHA
    9d51aac View commit details
Showing with 125 additions and 37 deletions.
  1. +2 −2 Directory.Build.props
  2. +2 −2 build/_build.csproj
  3. +79 −28 src/Reflectify/Reflectify.cs
  4. +9 −0 tests/.editorconfig
  5. +5 −5 tests/Reflectify.Specs/Reflectify.Specs.csproj
  6. +28 −0 tests/Reflectify.Specs/TypeMemberExtensionsSpecs.cs
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -32,11 +32,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.12.10">
<PackageReference Include="Roslynator.Analyzers" Version="4.13.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.186">
<PackageReference Include="Meziantou.Analyzer" Version="2.0.188">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
4 changes: 2 additions & 2 deletions build/_build.csproj
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Nuke.Common" Version="9.0.3" />
<PackageReference Include="Nuke.Components" Version="9.0.3" />
<PackageReference Include="Nuke.Common" Version="9.0.4" />
<PackageReference Include="Nuke.Components" Version="9.0.4" />
<PackageDownload Include="ReportGenerator" Version="[5.2.0]" />
<PackageDownload Include="GitVersion.Tool" Version="[6.0.2]" />
</ItemGroup>
107 changes: 79 additions & 28 deletions src/Reflectify/Reflectify.cs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
#nullable disable

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -599,7 +600,7 @@ public static BindingFlags ToBindingFlags(this MemberKind kind)
internal sealed class Reflector
{
private readonly List<FieldInfo> selectedFields = new();
private List<PropertyInfo> selectedProperties = new();
private readonly OrderedPropertyCollection selectedProperties = new();

public Reflector(Type typeToReflect, MemberKind kind)
{
@@ -611,40 +612,34 @@ public Reflector(Type typeToReflect, MemberKind kind)

private void LoadProperties(Type typeToReflect, MemberKind kind)
{
var collectedPropertyNames = new HashSet<string>();

while (typeToReflect != null && typeToReflect != typeof(object))
{
BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;

var allProperties = typeToReflect.GetProperties(flags);

AddNormalProperties(kind, allProperties, collectedPropertyNames);
AddNormalProperties(kind, allProperties);

AddExplicitlyImplementedProperties(kind, allProperties, collectedPropertyNames);
AddExplicitlyImplementedProperties(kind, allProperties);

AddInterfaceProperties(typeToReflect, kind, flags, collectedPropertyNames);
AddInterfaceProperties(typeToReflect, kind, flags);

// Move to the base type
typeToReflect = typeToReflect.BaseType;
}

selectedProperties = selectedProperties.Where(x => !x.IsIndexer()).ToList();
}

private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties,
HashSet<string> collectedPropertyNames)
private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties)
{
if (kind.HasFlag(MemberKind.Public) || kind.HasFlag(MemberKind.Internal) ||
kind.HasFlag(MemberKind.ExplicitlyImplemented))
{
foreach (var property in allProperties)
{
if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented() &&
collectedPropertyNames.Add(property.Name))
if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented())
{
selectedProperties.Add(property);
selectedProperties.AddNormal(property);
}
}
}
@@ -656,29 +651,22 @@ private static bool HasVisibility(MemberKind kind, PropertyInfo prop)
(kind.HasFlag(MemberKind.Internal) && prop.IsInternal());
}

private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties,
HashSet<string> collectedPropertyNames)
private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties)
{
if (kind.HasFlag(MemberKind.ExplicitlyImplemented))
{
foreach (var p in allProperties)
foreach (var property in allProperties)
{
if (p.IsExplicitlyImplemented())
if (property.IsExplicitlyImplemented())
{
var name = p.Name.Split('.').Last();

if (collectedPropertyNames.Add(name))
{
selectedProperties.Add(p);
}
selectedProperties.AddExplicitlyImplemented(property);
}
}
}
}

#pragma warning disable AV1561
private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags,
HashSet<string> collectedPropertyNames)
private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags)
{
if (kind.HasFlag(MemberKind.DefaultInterfaceProperties) || typeToReflect.IsInterface)
{
@@ -688,10 +676,9 @@ private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, Binding
{
foreach (var prop in interfaceType.GetProperties(flags))
{
if ((!prop.IsAbstract() || typeToReflect.IsInterface) &&
collectedPropertyNames.Add(prop.Name))
if (!prop.IsAbstract() || typeToReflect.IsInterface)
{
selectedProperties.Add(prop);
selectedProperties.AddFromInterface(prop);
}
}
}
@@ -734,4 +721,68 @@ private static bool HasVisibility(MemberKind kind, FieldInfo field)
public PropertyInfo[] Properties => selectedProperties.ToArray();

public FieldInfo[] Fields => selectedFields.ToArray();

private class OrderedPropertyCollection : IEnumerable<PropertyInfo>
{
private readonly Dictionary<string, PropertyKind> kindMap = new();
private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();

public IEnumerator<PropertyInfo> GetEnumerator()
{
return propertiesWithName.Select(x => x.Property).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

private enum PropertyKind
{
Normal,
ExplicitlyImplemented,
Interface
}

public void AddExplicitlyImplemented(PropertyInfo property)
{
var name = property.Name.Split('.').Last();

Add(name, property, PropertyKind.ExplicitlyImplemented);
}

public void AddNormal(PropertyInfo property)
{
Add(property.Name, property, PropertyKind.Normal);
}

public void AddFromInterface(PropertyInfo property)
{
Add(property.Name, property, PropertyKind.Interface);
}

private void Add(string name, PropertyInfo property, PropertyKind kind)
{
if (property.IsIndexer())
{
// We explicitly skip indexers
}
else if (!kindMap.TryGetValue(name, out var existingKind))
{
kindMap[name] = kind;
propertiesWithName.Add((name, property));
}
else if (existingKind == PropertyKind.ExplicitlyImplemented && kind == PropertyKind.Normal)
{
// Normal properties have priority over interface properties
kindMap[name] = kind;
propertiesWithName.RemoveAll(x => x.Name == name);
propertiesWithName.Add((name, property));
}
else
{
// Property with that name already exists
}
}
}
}
9 changes: 9 additions & 0 deletions tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
[*.cs]


# Purpose: Do not expose generic lists
# Reason: We don't care about this in tests
dotnet_diagnostic.CA1002.severity = none

# Purpose: Do not nest type GetProperties.
# Reason: We don't care about this in tests
dotnet_diagnostic.CA1034.severity = none
@@ -32,6 +37,10 @@ dotnet_diagnostic.CA1822.severity = none
# Reason: We don't care about this in tests
dotnet_diagnostic.CA1852.severity = none

# Prefer using collection abstraction instead of implementation
# Reason: We don't care about this in tests
dotnet_diagnostic.MA0016.severity = none

# Purpose: Elements must be documented
# Reason: Not needed for tests
dotnet_diagnostic.SA1600.severity = none
10 changes: 5 additions & 5 deletions tests/Reflectify.Specs/Reflectify.Specs.csproj
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
@@ -25,7 +25,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
@@ -36,10 +36,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.5" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="FluentAssertions" Version="8.1.1" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="coverlet.collector" Version="6.0.2" PrivateAssets="all">
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="all">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
28 changes: 28 additions & 0 deletions tests/Reflectify.Specs/TypeMemberExtensionsSpecs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using FluentAssertions;
using JetBrains.Annotations;
@@ -244,6 +245,33 @@ public void Can_find_all_members()
new { Name = "NormalField" },
]);
}

private interface ICollectionInterface
{
[UsedImplicitly]
IReadOnlyCollection<int> Items { get; }
}

private abstract class BaseCollection
{
public List<int> Items { get; } = new();
}

private sealed class CollectionImplementation : BaseCollection, ICollectionInterface
{
IReadOnlyCollection<int> ICollectionInterface.Items => Items;
}

[Fact]
public void Normal_properties_are_always_preferred_over_explicit_properties()
{
var properties = typeof(CollectionImplementation).GetProperties(
MemberKind.Public | MemberKind.ExplicitlyImplemented);

properties.Should().BeEquivalentTo([
new { Name = "Items", PropertyType = typeof(List<int>) }
]);
}
}

public class FindProperty