Rakete1111
Rakete1111

Reputation: 49018

How to use generics with classes full of constants?

I hope it becomes clear what I mean. I have multiple static class full of options:

static class Thing1
{
    public const string Name = "Thing 1";
    // More const fields here.
}

static class Thing2
{
    public const string Name = "Thing 2";
    // More const fields here.
}

Now I want to use those options to create a class which includes the contents of one of these classes.

public void Create<T>()
{
    var foo = new Foo(T.Name);
    foo.Prop = T.Something;

    if (T.HasValue)
        foo.Add(T.Value);
}

But of course this doesn't work. I would use interfaces, but static classes can't implement interfaces.

Is there any way to make this work elegantly? Making Thing1 and Thing2 singletons would work, but that isn't a very nice solution.

I could create a struct and put the objects into another static class, but I was wondering whether you could do something like the above.

Upvotes: 2

Views: 775

Answers (5)

Fabjan
Fabjan

Reputation: 13676

Well, you can create an interface and make your classes non-static and inherit from this interface:

public class Thing1 : IThing
{
    public string Name { get; } = "Thing 1";
    // More const fields here.
}

public class Thing2 : IThing
{
    public string Name { get; } = "Thing 2";
    // More fields here.
}

interface IThing 
{   
    string Name { get; }
}

And then use it for your method together with type parameter constraint:

public void Create<T>(T t) where T : IThing
{
    // Now compiler knows that `T` has all properties from `IThing`
    var foo = new Foo(t.Name);
    foo.Prop = t.Something;

    if (t.HasValue)
        foo.Add(t.Value);
}

Upvotes: 3

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112632

After some reflection I came up with another solution. Why select the constants by type? It is much easier if we use the same type to store the different sets of constants.

public class Constants
{
    public string Name { get; set; }
    public double Health { get; set; }
    public int? MaxTries { get; set; }
}

We then identify the sets through an enum:

public enum SetType
{
    Set1, // Please use speaking names in a real implementation!
    Set2,
    Set3
}

We define the values of the constants while creating the dictionary of constants sets:

public static readonly Dictionary<SetType, Constants> ConstantSets =
    new Dictionary<SetType, Constants> {
        [SetType.Set1] = new Constants { Name = "Set 1", Health = 100, MaxTries = null },
        [SetType.Set2] = new Constants { Name = "Set 2", Health = 80, MaxTries = 5 },
        ...
    };

The Create method becomes

public void Create(SetType set)
{
    var constants = ConstantSets[set];
    var foo = new Foo(constants.Name) {
        Health = constants.Health
    };
    if (constants.MaxTries is int maxTries) {
        foo.Add(maxTries);
    }
}

No generics, no reflection, no fancy stuff required.

Upvotes: 1

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186813

You can try Reflection: scan assemblies for static classes, obtain public const string fields with their values from them and materialize them as a Dictionary<T>

  using System.Linq; 
  using System.Reflection;

  ...

  // Key   : type + field name, say Tuple.Create(typeof(Thing1), "Name")
  // Value : corresponding value, say "Thing 1";
  static Dictionary<Tuple<Type, string>, string> s_Dictionary = AppDomain
    .CurrentDomain
    .GetAssemblies() // I've taken all assemblies; you may want to add Where here
    .SelectMany(asm => asm.GetTypes())
    .Where(t => t.IsAbstract && t.IsSealed) // All static types, you may want to add Where
    .SelectMany(t => t
       .GetFields()                         // All constant string fields
       .Where(f => f.FieldType == typeof(string))
       .Where(f => f.IsPublic && f.IsStatic)
       .Where(f => f.IsLiteral && !f.IsInitOnly) // constants only
       .Select(f => new { 
         key = Tuple.Create(t, f.Name),
         value = f.GetValue(null)
       }))
    .ToDictionary(item => item.key, item => item.value?.ToString());

If you want to scan not all loaded but just one (executing) assembly

  static Dictionary<Tuple<Type, string>, string> s_Dictionary = Assembly
    .GetExecutingAssembly()
    .GetTypes()
    .Where(t => t.IsAbstract && t.IsSealed) 
    ...

Now you can wrap the dictionary, say

  public static string ReadConstant<T>(string name = null) {
    if (string.IsNullOrEmpty(name))
      name = "Name";

    if (s_Dictionary.TryGetValue(Tuple.Create(typeof(T), name), out string value))
      return value;
    else
      return null; // Or throw exception
  }

Usage

 string name1 = ReadConstant<Thing1>();

Upvotes: 2

juharr
juharr

Reputation: 32296

Instead of a static classes with constants. You can create a class with properties and static instances with the desired values.

public class Thing
{
    private Thing(string name, string something, bool hasValue, string value)
    {
        Name = name;
        Something = something;
        HasValue = hasValue;
        Value = value;
    }

    public string Name { get; }
    public string Something{ get; }
    public bool HasValue { get; }
    public string Value{ get; }

    public static Thing Thing1 { get; } = new Thing("Thing1", "Something1", true, "Value1");
    public static Thing Thing2 { get; } = new Thing("Thing2", "Something2", false, null);
}

And then your method would just take that class.

public void Create(Thing t)
{
    var foo = new Foo(t.Name);
    foo.Prop = t.Something;

    if (t.HasValue)
        foo.Add(t.Value);
}

Then you'd call it with either

Create(Thing.Thing1);

or

Create(Thing.Thing2);

Upvotes: 1

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112632

You could use non static classes and add those to a dictionary having a Type key. But you would have to use read-only properties.

public interface IConstants
{
    string Name { get; }
    double InitialHealth { get; }
    public int? MaxTries { get; }
}

public class Thing1 : IConstants
{
    public string Name => "Thing 1";
    public double InitialHealth => 100.0;
    public int? MaxTries => null;
}

public class Thing2 : IConstants
{
    public string Name => "Thing 2";
    public double InitialHealth => 80.0;
    public int? MaxTries => 10;
}

Initialize the dictionary:

public static readonly Dictionary<Type, IConstants> Constants =
    new Dictionary<Type, IConstants> {
        [typeof(Thing1)] = new Thing1(),
        [typeof(Thing2)] = new Thing2(),
    };

The Create function:

public void Create<T>()
{
    Type key = typeof(T);
    var foo = new Foo(key.Name);
    IConstants constants = Constants[key];
    foo.InitialHealth = constants.InitialHealth;

    if (constants.MaxTries is int maxTries) { // Only true if MaxTries.HasValue.
                                                // Converts to int at the same time.
        foo.Add(maxTries);
    }
}

Upvotes: 1

Related Questions