0

Say I have this class with a few members, for example (this is a contrived example, I'd rather no have a discussion about the intricacies of the real-life design. I really just want to convey the general idea here.):

public class Address
{
    public Guid Id { get; set; }
    public Guid? HouseId { get; set; }
    public Guid? FlatId { get; set; }
    public Guid? SomeOtherBuildingTypeId { get; set; 
}

Now as it happens there exist 3 methods to create an Address:

public void CreateAddressForHouse();
public void CreateAddressForFlat();
public void CreateAddressForSomeOtherBuildingType();

Under the surface this group of methods does the exact same thing, bar setting a different Id property in the Address class. This is causing quite some code duplication in the real life application and I want to rewrite this to something more general.

In my mind I can pass the name of the required property and its value to a CreateAddress function, in something like a Func. But I'm seriously lacking in this respect, where to start? What .NET stuff can I use out of the box? Or what specific keywords should I look for?

Apeiron
  • 602
  • 6
  • 17
  • 2
    Pack everything the methods do "exactly [the] same" into a separate method `CreateAddress()` (probably `private`) and call that from those three methods? Or better yet, have one `Guid BuildingTypeID` and one `BuildingType BuildingType` being a value of the `enum BuildingType { House, Flat, SomethingElse, YetAnotherThing }`, which seems far more flexible than a new property for every new type of building. – Corak Aug 24 '15 at 10:26
  • I'm not happy with this property-per type, it's a result of having some tables with a lot of nullable FKs :( – Apeiron Aug 24 '15 at 11:36

3 Answers3

2

You can use a MemberExpression:

public void CreateAddress(Expression<Func<Address, Guid?>> member)
{
    // Get the property from the expression
    var propertyInfo = GetPropertyInfo(this, member);

    // Create a new address
    var guid = Guid.NewGuid();

    // Assign it to the property of this instance
    propertyInfo.SetValue(this, guid);
}

Then you call the method like this, using a lambda a => a.PropertyName:

var address = new Address();
address.CreateAddress(a => a.HouseId);
Console.WriteLine(address.HouseId);

See Retrieving Property name from lambda expression for the implementation of GetPropertyInfo. It gets the PropertyInfo of the member specified in the lambda expression (and checks that it is indeed a property), which you can use to set the property in the CreateAddress method.

Apart from that, @Corak's suggestion is a valid one. Maybe you shouldn't use a property per address type, but use a Dictionary<AddressType, Guid?> property. That may or may not be viable depending on the class design and its intended usage.

Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • I wish I could, but this is a direct result of having some tables with a lot of nullable FKs. – Apeiron Aug 24 '15 at 11:37
  • By using this I've managed to refactor & centralize a lot of duplicate code in various cases! Spent a few days writing tests & refactoring and this really helped alot! – Apeiron Aug 28 '15 at 06:34
2

You can use expression trees to simplify your problem:

public class AddressService
{
    public Address CreateAddress(Expression<Func<Address, Guid?>> idPropertySelector)
    {
        // So you get the property info to later set it using reflection
        MemberExpression propertyExpr = (MemberExpression)idPropertySelector.Body;
        PropertyInfo property = (PropertyInfo)propertyExpr.Member;

        // Then you create an instance of address...
        Address address = new Address();

        // and you set the property using reflection:
        property.SetValue(address, (Guid?)Guid.NewGuid());

        return address;
    }
}

Now, who knows where in your code, this will work:

AddressService service = new AddressService();
Address address = service.CreateAddress(a => a.FlatId);
Guid? flatId = address.FlatId; // This will be already assigned!
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • This (`(MemberExpression)idPropertySelector.Body`) will throw when called with `a => new Guid()` (or anything that isn't a MemberExpression, basically). – CodeCaster Aug 24 '15 at 10:42
  • 1
    @CodeCaster Sometimes I feel that SO answers shouldn't fit all use cases nor pay attention to all side effects. At the end of the day, we're not a coding service... If the OP gets the idea, it's enough. What's your opinion? – Matías Fidemraizer Aug 24 '15 at 10:53
  • You're absolutely right. We don't have to provide fool-proof, copy-pasteable code. I just commented it as a warning. This code is not user-friendly nor should it be used as-is (at least, if you care about (re)usability and relevant exceptions when using the code wrong). But that goes for most code on SO. :-) – CodeCaster Aug 24 '15 at 11:38
  • Very nice stuff this Expression api. Should look into this :) – Apeiron Aug 24 '15 at 11:48
  • @CodeCaster That bullet-proof code should be on some GitHub repository or who knows. At the end of the day, at least for me, SO is a bunch of ideas to get some direction to learn more about some topic. I don't like providing full solutions with all details, because you need some challengue to go beyond your own knowledge and skills, and copy-paste solutions aren't for this – Matías Fidemraizer Aug 24 '15 at 12:55
0

You can add a property BuildingType BuildingType being a value of the enum BuildingType { House, Flat, SomeOtherBuildingType, YetAnotherThing } as suggested by Corak. To make it simpler, you can create a parameterized constructor in Address class:

public Address(Guid? id,BuildingType type)
{
 switch(type)
 {
  case BuildingType.House:
                   HouseId=id;
                   break; 
  case BuildingType.Flat:
                   FlatId=id;
                   break;
  case BuildingType.SomeOtherBuildingType:
                   SomeOtherBuildingTypeId =id;
                   break;
  default:
        break;
 }
}

This way it will be easier to extend. Also, you need not to have so many methods. Only one CreateAddress() can be used to generate address of multiple types.

Nikita Shrivastava
  • 2,978
  • 10
  • 20