tags:

views:

284

answers:

8

Hey, I think I have the wrong idea here, but I'm not sure what is best. I want a class with a member variable that can be of any type, depending on what is needed at the time. So far, I have something like this:

    public class ConfigSetting<T> {
    private T value;

    public T GetValue() {
        return value;
    }
    public void ChangeValue() {

    }

    public ConfigSetting(string heading, string key) {
        this.value = DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;
    }
}

The type returned by the right side of the 'this.value' line is a string, currently. I know here it seems like I have no need to use anything other than the string type, but eventually I will expand the constructor, such that 'this.value' could be a string, int, float, or bool.

Anyway, my compiler says "Cannot convert 'string' to 'T'", so I assume I'm doing something very backwards.

Thank you.

+2  A: 

You need to cast the returned String to T:

public ConfigSetting(string heading, string key) {
    this.value = (T)DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;
}
Oded
This does not work, same error.I would have said I'd already tried that, but I guessed the problem was deeper than this.
Motig
+5  A: 

Well, what conversion did you expect it to apply? If you expect the value to already be of the right type, you could do:

object tmp = DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;
this.value = (T) tmp;

Note that you have to go through object either implicitly (as here) or with an explicit cast:

this.value = (T)(object) DerivedMethods.configsettings... (etc);

The set of conversions provided for generic types is somewhat limited. But it should work if the original value is genuinely correct.

Jon Skeet
Thank you. This compiles (though I have yet to test it).I have two questions:1: Why do I have to go through object?2: What happens after this if I try to use String methods on value? Does c# now know that value is of type String?
Motig
doesn't RawValue always return a string? wouldn't the conversion to int, long, etc fail?
bendewey
Yes, but like I said in my original question, I intend to expand the constructor.
Motig
@Motig: No, C# won't know that it's of type string - it's of type `T`. The reasons for other conversions not being allowed are fairly complex and I wouldn't claim to understand them all, but the long and the short of it is that if the compiler complains that a conversion isn't available, go via `object` :)
Jon Skeet
@skeet-lol at the comment very awesome way of putting it. "but the long and the short of it is that if the compiler complains that a conversion isn't available, go via object :)". Same goes for the married folks, if the wife complains about not having this or that, to get rid of the complaints just hand her your paycheck!
JonH
A: 

It looks like you're trying to assign a string (configsettings.SettingGroups[heading].Settings[key].RawValue) to the generic member. You'll need to provide some way to convert the string to type T -- usually through casting. For example:

this.value = (T)DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;
Justin R.
A: 

In this line:

this.value = DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;

you are setting your variable of type T to a string value. The RawValue method returns a string. You need to explicitly cast it to type T.

this.value = (T) (object) DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;

Is T really only going to be only values without constructors? You might be able to make that explicit and avoid the object casting, if you want to avoid that for some reason.

Patrick Karcher
+1  A: 

I think you shouldn't necessarily use generics to plan ahead for all possible future scenarios because you will likely run into edge cases in the future and have to modify the code regardless.

However if you already have multiple scenarios that a generic would fit to, then you can benefit from the logic reuse now and can test them all properly.

I see others have already provided code answers so I'll stop here.

John K
+1  A: 

I assume that

DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue

is a string.

You can't assign a string to this.value because the type of this.value is T and could be anything, such as types to which strings are not assignable.

One solution is to remove the generic parameter and make value a string, and then provide different ways of accessing it which parse bools, floats, or whatever as required.

public float GetFloatValue {
    get { return float.Parse(this.value); }
}

// etc
recursive
A: 

Try a

string strValue = DerivedMethods.configsettings.SettingGroups[heading].Settings[key].RawValue;
this.value = (T)Convert.ChangeType(strValue, typeof(T), CultureInfo.InvariantCulture)

if you have your configuration values stored as strings and want it converted to the right type. This only works for most of the primitive types like Int32, Double, etc.

Rauhotz
+5  A: 

You're running into problems because this is not a good use of generics. If the generic type parameter can only be constructed in four different ways -- string, float, bool and int -- then this isn't very generic. I expect that a generic thing can be any type at all.

If I had a thing that could only be one of four types then I would model it like this:

abstract class ConfigSetting
{ /* shared code here */  }

class TextSetting : ConfigSetting
{ /* Code here to handle string settings */ }

class BooleanSetting : ConfigSetting
{ /* ... 

and so on. I would probably then give each of them an internal constructor, and make the base class into a factory for the derived classes, using the factory pattern.

Only use generics if your solution is truly generic. Like List<T>, for example, can be a list of anything: ints, strings, arrays, dictionaries, functions, whatever. If the thing you are modeling has a small number of possible types, just make one for each type.

Eric Lippert