1
votes

I am trying to refactor some code and have to admit that I am new to the concept of generics.

I have a base class:

BaseVarDef.cs:

using UnityEngine;
public abstract class BaseVarDef<T> : ScriptableObject
{
    [Multiline]
    public string DeveloperDescription = "";

    public T Value;

    public void SetValue(T value)
    {
        Value = value;
    }

}

Then I have several classes that derive from this. But they all contain the same 3 methods that I would like to refactor into this generic base class. The only difference is they expect either the generic type or class that is currently defining them.

FloatVar.cs

using UnityEngine;
[CreateAssetMenu(fileName = "New FloatVar", menuName = "Variables/FloatVar")]
public class FloatVar : BaseVarDef<float>
{
    public void SetValue(FloatVar value)
    {
        Value = value.Value;
    }
    public void ApplyChange(float amount)
    {
        Value += amount;
    }
    public void ApplyChange(FloatVar amount)
    {
        Value += amount.Value;
    }
}

StringVar.cs

using UnityEngine;
[CreateAssetMenu(fileName = "New StringVar", menuName = "Variables/StringVar")]
public class StringVar : BaseVarDef<string>
{
    public void SetValue(StringVar value)
    {
        Value = value.Value;
    }
    public void ApplyChange(string amount)
    {
        Value += amount;
    }
    public void ApplyChange(StringVar amount)
    {
        Value += amount.Value;
    }
}

Is there a way to refactor the: SetValue and the 2 overloaded ApplyChange methods into BaseVarDef.cs?

Thanks in advance

2

2 Answers

4
votes

I think you can define all Apply and Set in Base class

public abstract class BaseVarDef<T>
{
    public string DeveloperDescription = "";

    public T Value;

    public void SetValue(T value)
    {
        Value = value;
    }
    public void SetValue(BaseVarDef<T> value)
    {
        Value = value.Value;
    }
    public void ApplyChange(T amount)
    {
        AddValue(amount);
    }
    public void ApplyChange(BaseVarDef<T> amount)
    {
        AddValue(amount.Value);
    }

    protected abstract void AddValue(T val);
}

public class FloatVar : BaseVarDef<float>
{
    protected override void AddValue(float val)
    {
        Value += val;
    }
}
1
votes

If you can just add those methods to the original base class (and then remove the abstract from it), and then you just do BaseVarDef<string> or BaseVarDef<float>. No more need for the FloatVar or StringVar classes!

The tricky part here is the += operator, which can't be applied to generics (at least there is no constraint I'm aware of that allows you to specify the type implements an operator).

So you could either make an abstract Add method that derived classes must implement as Svetlana describes, or you can use dynamic as shown below (and get rid of the need for derived classes altogether).

The one problem with dynamic is that there is no compile time checking with dynamic types, so it's not foolproof. The type T must overload the + operator or you'll get a runtime exception. You could avoid that by wrapping the line Value = amt + val; in a try/catch, but then you may get unexpected behavior.

public class BaseVarDef<T>
{
    public string DeveloperDescription { get; set; } = "";

    public T Value { get; private set; }

    public void SetValue(T value)
    {
        Value = value;
    }

    public void SetValue(BaseVarDef<T> value)
    {
        Value = value.Value;
    }

    public void ApplyChange(T amount)
    {
        AddToValue(amount);
    }

    public void ApplyChange(BaseVarDef<T> amount)
    {
        AddToValue(amount.Value);
    }

    private void AddToValue(T amount)
    {
        dynamic amt = amount;
        dynamic val = Value;

        // Empty catch to avoid runtime exception if 'T' doesn't support the '+' operator
        try { Value = amt + val; }
        catch { }           
    }
}