SkillAgentSearch skills...

Albedo

A .NET library targeted at making Reflection programming more consistent, using a common set of abstractions and utilities

Install / Use

/learn @AlbedoOrg/Albedo
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Build status NuGet version

Announcement

Recently the ownership of this project has been passed from Mark Seemann to maintainers of the AlbedoOrg organization. To reflect that change the default namespace prefix and assembly name were changed from Ploeh.Albedo to Albedo. Please use the text replace feature of your IDE to quickly fix all the namespace imports.

Albedo

A .NET library targeted at making Reflection programming more consistent, using a common set of abstractions and utilities.

This examples uses a PropertyInfo to read a value off a System.Version instance:

PropertyInfo pi = from v in new Properties<Version>()
                  select v.Minor;
var version = new Version(2, 7);
var visitor = new ValueCollectingVisitor(version);

var actual = new PropertyInfoElement(pi).Accept(visitor);

Assert.Equal(version.Minor, actual.Value.OfType<int>().First());

More examples further down.

Albedo follows Semantic Versioning 2.0.0.

Where do you get it?

Obviously, the source code is available here on GitHub, but you can download the compiled library with NuGet.

What problem does it address?

Albedo addresses the problem that the .NET Reflection API (mainly in System.Reflection) doesn't provide a set of good abstractions. As an example, both PropertyInfo and FieldInfo expose GetValue and SetValue functions, yet despite their similarities, these functions are defined directly on each of those two classes, so there's no polymorphic API to read a value from a property or field, or assign a value to a property or field.

It's also difficult to extract the type of a property or field in a polymorphic manner, because the type of a property is defined by the PropertyType property, while the type of a field is defined by the FieldType property.

At least PropertyInfo and FieldInfo both derive from the abstract MemberInfo class, so they still have a little in common. However, if you want to compare any of these to, say, a ParameterInfo instance, the highest common ancestor is Object!

While you can define your own interfaces or delegates to deal with this lack of polymorphism in the Reflection API, Albedo offers a common set of abstractions over the Reflection API. These abstractions are based on tried-and-true design patterns, and as the code examples below demonstrate, are very flexible.

Who cares?

People who write lots of Reflection code might benefit from Albedo: Programmers of

  • ORMs
  • Auto-Mappers
  • dynamic mock libraries
  • DI Containers
  • unit testing frameworks
  • etc.

Instead of defining a constrained and specific set of interfaces for each such tool, Albedo can provide a common, reusable abstraction over Reflection, enabling a more open architecture.

Albedo was born out of a need for such abstractions in AutoFixture. While it's easy to extract a specific interface to address a specific need, you always run the risk of defining an interface that can only be used in one very specific situation; such an interface may not be a good abstraction.

If you don't write a lot of Reflection code, you probably don't need Albedo.

How does it work?

In OOD, whenever you find yourself in a situation where you need to provide a consistent API over a final, known set of concrete classes, the much-derided Visitor pattern is very useful. Albedo is based on an IReflectionVisitor<T> interface that visits Adapters over the known Reflection types, such as PropertyInfo, ParameterInfo, etc.

While Albedo defines the IReflectionVisitor<T> interface, it also provides a ReflectionVisitor<T> abstract base class you can use to visit only those IReflectionElement Adapters you care about.

All examples shown here can be found in the Scenario class in the Albedo code base's unit tests.

Collecting values

The initial example uses a sample ValueCollectingVisitor, which is implemented like this:

public class ValueCollectingVisitor : ReflectionVisitor<IEnumerable>
{
    private readonly object target;
    private readonly object[] values;

    public ValueCollectingVisitor(object target, params object[] values)
    {
        this.target = target;
        this.values = values;
    }

    public override IEnumerable Value
    {
        get { return this.values; }
    }

    public override IReflectionVisitor<IEnumerable> Visit(
        FieldInfoElement fieldInfoElement)
    {
        var value = fieldInfoElement.FieldInfo.GetValue(this.target);
        return new ValueCollectingVisitor(
            this.target,
            this.values.Concat(new[] { value }).ToArray());
    }

    public override IReflectionVisitor<IEnumerable> Visit(
        PropertyInfoElement propertyInfoElement)
    {
        var value = 
            propertyInfoElement.PropertyInfo.GetValue(this.target, null);
        return new ValueCollectingVisitor(
            this.target,
            this.values.Concat(new[] { value }).ToArray());
    }
}

Notice that this Visitor only collects information about the values of properties and fields, and not (say) the return value of method calls. (Since this is sample code, it implicitly assumes that PropertyInfo.GetValue will succeed, which will not be the case if the property is a write-only property. However, it's trivial to add a check to see if the property can be read.)

Note: ValueCollectingVisitor has turned out to be such a generally useful implementation that it's now available by default in Albedo. Thus, you don't have to implement it yourself, but remains here as an example of how to use Albedo.

Here's a more complicated example that uses the ValueCollectingVisitor to read all public properties and fields from a TimeSpan instance:

var ts = new TimeSpan(2, 4, 3, 8, 9);
var elements = ts.GetType().GetPublicPropertiesAndFields().ToArray();

var actual = elements.Accept(new ValueCollectingVisitor(ts));

var actualValues = actual.Value.ToArray();
Assert.Equal(elements.Length, actualValues.Length);
Assert.Equal(1, actualValues.Count(ts.Days.Equals));
Assert.Equal(1, actualValues.Count(ts.Hours.Equals));
Assert.Equal(1, actualValues.Count(ts.Milliseconds.Equals));
Assert.Equal(1, actualValues.Count(ts.Minutes.Equals));
Assert.Equal(1, actualValues.Count(ts.Seconds.Equals));
Assert.Equal(1, actualValues.Count(ts.Ticks.Equals));
Assert.Equal(1, actualValues.Count(ts.TotalDays.Equals));
Assert.Equal(1, actualValues.Count(ts.TotalHours.Equals));
Assert.Equal(1, actualValues.Count(ts.TotalMilliseconds.Equals));
Assert.Equal(1, actualValues.Count(ts.TotalMinutes.Equals));
Assert.Equal(1, actualValues.Count(ts.TotalSeconds.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.MaxValue.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.MinValue.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.TicksPerDay.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.TicksPerHour.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.TicksPerMillisecond.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.TicksPerMinute.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.TicksPerSecond.Equals));
Assert.Equal(1, actualValues.Count(TimeSpan.Zero.Equals));

The ValueCollectingVisitor class is the only custom code written to implement this behaviour. The rest of the types in the above code example is either defined in the BCL (TimeSpan, etc.) or provided by Albedo (PropertyInfoElement, FieldInfoElement, CompositeReflectionElement).

Assigning values

Albedo wouldn't be a truly flexible library if the only thing it could do is to read values from properties and fields. It can also do other things; closely related to reading values is assigning values.

Assume that you have a class like this:

    public class ClassWithWritablePropertiesAndFields<T>
    {
        public T Field1;

        public T Field2;

        public T Property1 { get; set; }

        public T Property2 { get; set; }
    }

If you want to assign a value to all fields and properties, you can do it like this:

var t = new ClassWithWritablePropertiesAndFields<int>();
var elements = t.GetType().GetPublicPropertiesAndFields().ToArray();

var actual = elements.Accept(new ValueWritingVisitor(t));
actual.Value(42);
            
Assert.Equal(42, t.Field1);
Assert.Equal(42, t.Field2);
Assert.Equal(42, t.Property1);
Assert.Equal(42, t.Property2);

Apart from ClassWithWritablePropertiesAndFields<T>, the only custom type you'd have to provide to enable this feature is the ValueWritingVisitor:

public class ValueWritingVisitor : ReflectionVisitor<Action<object>>
{
    private readonly object target;
    private readonly Action<object>[] actions;

    public ValueWritingVisitor(
        object target,
        
View on GitHub
GitHub Stars168
CategoryDevelopment
Updated1y ago
Forks17

Languages

C#

Security Score

80/100

Audited on Nov 22, 2024

No findings