Assigning properties based on string parameters

Posted on

Problem

I am using the below method to assign value to my object(Team.Driver/Team.Codriver). This works fine but just wanted to know if I could improve this code via LINQ or other options.

public void MoveDriverCodriver(
    Team source, string sourceType, Team target, string targetType) 
{
    if (sourceType == "Driver")
    {
        if (targetType == "Driver")
        {
            target.Driver = source.Driver;
        }
        else
        {
            target.Codriver = source.Driver;
        }
    }
    else
    {
        if (targetType == "Driver")
        {
            target.Driver = source.Codriver;
        }
        else
        {
            target.Codriver = source.Codriver;
        }
    } 
}

Also second part is I want to update target Driver/Codriver details with the appropriate source. But I want to retain the id’s of target Driver/Codriver.

class Driver
{
  public Guid Id { get; set; }
  public string Firstname { get; set; }
  public string Lastname { get; set; }
  public int Age { get; set; }
}

class Team
{
  public Guid Id { get; set; }
  public Driver Driver { get; set; }
  public Driver Codriver { get; set; }
}

calling move operation.

Team team1 = new Team(); //Do some operation on team1
Team team2 = new Team(); //Do some operation on team2
MoveDriverCodriver(team1, "Driver", team2, "Codriver"); //Here I am trying to assign team1's driver to team2's codriver.

Solution

Making the assumption you can’t just use team1.Driver = team2.Codriver because you don’t know the name of the properties first hand, I would build an even more generic function where it can be used with any type.

Also, I wouldn’t name it Move because there is no move involved since the source still retains its values,

That said, my approach would be:

public void CopyProperty<T>(T source, string sourcePropertyName, T target, string targetPropertyName)
{
    if (source == null)
    {
        throw new ArgumentNullException(nameof(source));
    }

    if (target == null)
    {
        throw new ArgumentNullException(nameof(target));
    }

    if (string.IsNullOrWhiteSpace(sourcePropertyName))
    {
        ThrowInvalidParameterNameException(nameof(sourcePropertyName));
    }

    if (targetPropertyName == null)
    {
        ThrowInvalidParameterNameException(nameof(targetPropertyName));
    }

    Type elementType = typeof(T);
    PropertyInfo sourceProperty = elementType.GetProperty(sourcePropertyName);
    if (sourceProperty == null)
    {
        ThrowPropertyDoesNotExistException(sourcePropertyName, nameof(sourcePropertyName), elementType.Name);
    }

    if (!sourceProperty.CanRead)
    {
        ThrowPropertyAccessException(sourcePropertyName, PropertyAccessType.Read);
    }

    PropertyInfo targetProperty = elementType.GetProperty(targetPropertyName);
    if (targetProperty == null)
    {
        ThrowPropertyDoesNotExistException(targetPropertyName, nameof(targetPropertyName), elementType.Name);
    }

    if (!targetProperty.CanWrite)
    {
        ThrowPropertyAccessException(targetPropertyName, PropertyAccessType.Write);
    }

    if (sourceProperty.PropertyType != targetProperty.PropertyType)
    {
        throw new Exception(string.Concat("Property ", sourcePropertyName, " is not the same type as Property ", targetPropertyName));
    }

    targetProperty.SetValue(target, sourceProperty.GetValue(source, Type.EmptyTypes), Type.EmptyTypes);
}

private void ThrowInvalidParameterNameException(string parameterName)
{
    throw new ArgumentException("Invalid parameter name ", parameterName);
}

private void ThrowPropertyDoesNotExistException(string propertyParameter, string propertyParameterName, string typeName)
{
    throw new ArgumentException(string.Concat("Property ", propertyParameter, " does not exist in type ", typeName, " "), propertyParameterName);
}

private enum PropertyAccessType
{
    Read,
    Write
}

private void ThrowPropertyAccessException(string propertyParameter, PropertyAccessType accessType)
{
    throw new AccessViolationException(string.Concat("Property: ", propertyParameter, ", Access type: ", accessType));
}

With this you would get a safe and generic method to copy properties where you can use with any type and don’t know which property is going to be copied (does not account for indexed properties).

You can use it calling:

CopyProperty(team1, "Driver", team2, "Codriver");

There is no collections to query for (unless you are searching for the right property) so I don’t see how you can use linq.

This however has more if’s than your first attempt.

You could at least simplify the code as follows:

public static void MoveDriverCodriver(Team source, string sourceType, Team target, string targetType)
{     
  Driver sourceDriver = sourceType == "Driver" ? source.Driver : source.Codriver;
  if (targetType == "Driver")
  {
     target.Driver = sourceDriver;
  }
  else
  {
     target.Codriver = sourceDriver;
  }
}

I’m not sure how you’d use LINQ for this, maybe someone else can chime in on that. On the other hand, readability is sometimes more important.

Following is the change that I have done to simplify:-

public void MoveDriverCodriver(Team source, string sourceType, Team target, string targetType) 
{
    Move(sourceType == "Driver" ? source.Driver : source.Codriver, targetType == "Driver" ? target.Driver : target.Codriver);
}

public void Move(Driver source, Driver target) 
{
    source = target;
}

Leave a Reply

Your email address will not be published. Required fields are marked *