Reputation: 938
I have an interface with a bunch of string
properties:
public interface INameable
{
string Name { get; }
string Surname { get; }
}
And a class that happens to have the same properties, without however implementing the interface. Let's assume that I don't have access to modify the class (i.e. that it is implemented in an external DLL):
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
I have written a generic class that, given an object of the external class, will return to me an instantiation of that interface with all the values of the object:
public T ExtractInterface<T>(object argument) where T : class
{
var mock = new Mock<T>();
foreach (var property in typeof(T).GetProperties())
{
var returnValue = argument.GetType().GetProperties().SingleOrDefault(p => string.Equals(p.Name, property.Name, StringComparison.OrdinalIgnoreCase))?.GetValue(argument);
ParameterExpression value = Expression.Parameter(typeof(T), "value");
Expression setupProperty = Expression.Property(value, property.Name);
var func = Expression.Lambda<Func<T, string>>(setupProperty, value);
mock.Setup(func).Returns((string)returnValue);
}
return mock.Object;
}
This can be used like this:
var person = new Person { Name = "Joe", Surname = "Blogs" };
var personWithInterface = ExtractInterface<INameable>(person);
personWithInterface.Name.Dump();
personWithInterface.Surname.Dump();
The problem with this is that it only works with string
properties. Can someone please help me modify the method so that it works with properties returning any type?
Upvotes: 0
Views: 155
Reputation: 29252
This would be much easier, even if you have to do it over and over. Unlike using reflection, you won't get a runtime error if the class and the interface no longer match by coincidence. It just won't compile.
public class PersonWrapper : INameable
{
private readonly Person _person;
public PersonWrapper(Person person)
{
_person = person ?? throw new ArgumentNullException(nameof(person));
}
public string Name => _person.Name;
public string Surname => _person.Surname;
}
If you do something "generic" with reflection, this will compile:
var list = new List<string>();
INameable person = ExtractInterface<INameable>(list);
Unless we have absolutely no choice, anything that "tricks" the compiler so that we can compile things that shouldn't compile is a recipe for trouble. It takes away one of the most powerful tools we have to prevent runtime errors.
This is essentially the same approach used to "adapt" HttpContext
to HttpContextBase
, HttpRequest
to HttpRequestBase
, etc., except that those are abstract classes instead of interfaces.
The original implementations didn't implement an interface or inherit from an abstract class, but it later became apparent that it would be helpful if there were abstractions (using the term loosely) for those classes.
So they created new abstract classes with exactly the same properties as the original classes, just as you created new interfaces for some existing classes.
They didn't use reflection to map the existing classes to the abstract classes. They just did used a wrapper which behaves just like the code above.
Upvotes: 1