KPA
KPA

Reputation: 93

C# - Replace Struct with Class and subclasses

I want to replace the struct in the following code with a parent class with no data members and four subclasses, each subclass adds a different field. e.g. The SMValueFlt subclass adds a field named fltValue, and so on.

I am very new to C# and my Java is very rusty, so this is proving harder than I thought. And beyond actually setting up the class and subclasses i'm not sure how to proceed. Any help would be appreciated.

public class Interpreter {


    enum TypeCode { None, IntType, FloatType, StringType };


    struct SMValue {
        public TypeCode t;
        public int intValue;
        public float fltValue;
        public string strValue;
        public SMValue( int i )    {
            t = TypeCode.IntType;  intValue = i;  fltValue = 0.0F;  strValue = null; }
        public SMValue( float f )  {
            t = TypeCode.FloatType;  fltValue = f;  intValue = 0;  strValue = null; }
        public SMValue( string s ) {
            t = TypeCode.StringType;  strValue = s;  intValue = 0;  fltValue = 0.0F; }
        public override string ToString() {
            if (t == TypeCode.IntType) return String.Format("{0}", intValue);
            if (t == TypeCode.FloatType) return String.Format("{0}", fltValue);
            if (t == TypeCode.StringType)
                return strValue==null? "--null--" : strValue;
            return "???";
        }
    }
}

Upvotes: 1

Views: 1196

Answers (2)

supercat
supercat

Reputation: 81179

The semantics of a struct with exposed fields are fundamentally different from those of a class. Fundamentally, each structure-type variable holds a bunch of fields stuck together with duct tape, while a class-type variable holds a not-necessarily-unique reference to a class object. If a structure type has two int fields, and one has two variables of that type, one has four integers which may be written independently. By contrast, if a class type has two int fields and one has two variables of that type, it's possible that the variables may at any given time reference different instances (in which case they would encapsulate a total of four independently-writable integers), or they may identify the same instance (in which case both variables would identify the same pair of integers, and so writing the first number in one pair would also write the first number in the other).

Some people think all types should behave like class objects, and regard as "evil" any types that don't. In reality, there are situations where it's useful to stick a bunch of variables together with duct tape (so they may be passed around as a unit when convenient), but guarantee that every bunch of variables is distinct. Class types can be used to mimic this behavior, awkwardly, but structures naturally work that way.

Without knowing exactly how you intend to use your type, it's hard to say whether a class will be able to fulfill your needs without having to rework all your client code. It's important to note, however, that any class used to replace a struct must almost always be immutable. If you can't easily convert your struct to a mutable class, you'll probably have to keep it a struct.

Upvotes: 0

scottt732
scottt732

Reputation: 3937

I kept your TypeCode around in the first example, but it's not really necessary. You can inspect the type of a variable at runtime. For example,

var x = new SMFltValue() // (x.GetType() == typeof(SMFltValue)) = true, x is SMFltValue = true

Without using generics:

public enum TypeCode { IntType, FloatType, StringType };

public abstract class SMValue {
    public TypeCode t;

    public SMValue(TypeCode typeCode) {
        t = typeCode;
    }

    public abstract string ToString();
}

public class SMFltValue : SMValue {
    public float fltValue;

    public SMFltValue(float f) : base(TypeCode.FloatType)
    {
        fltValue = f;
    }

    public override string ToString() 
    {
        return String.Format("{0}", fltValue);
        return String.Format("{0}", intValue);
        return strValue==null ? "--null--" : strValue;
    }
}

public class SMIntValue : SMValue {
    public int intValue;

    public SMIntValue(int i) : base(TypeCode.IntType)   
    {
        intValue = i;
    }

    public override string ToString() 
    {
        return String.Format("{0}", intValue);
    }
}

public class SMStrValue : SMValue {
    public string strValue;

    public SMStrValue(string s) : base(TypeCode.StringType)
    {
        strValue = s;
    }

    public override string ToString() 
    {
        return strValue==null ? "--null--" : strValue;
    }
}

But generics would make it much nicer.

public class SMValue<T> {
    public T value;

    public SMValue(T value) {
        this.value = value;
    }

    public string ToString() {
        if (value == null) 
        {
            return "--null--";                  
        }
        else 
        {
            return string.Format("{0}", value);
        }
    }
}

Then you could use it as.

int i = 3;
float f = 5.0f;
string s = null;

new SMValue<int>(i).ToString() ==> 3
new SMValue<float>(f).ToString() ==> 5.0
new SMValue<string>(s).ToString() ==> "--null--"

The <int>, <float>, <string> aren't actually necessary because the compiler can infer the type from the variable being passed to the constructor.

Upvotes: 2

Related Questions