Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# simulating a global scope to contain user-defined but runtime-constant delegate arrays

I have some delegates, two classes and a struct that look kind of like this:

delegate Value Combination(Value A, Value B);

class Environment
{
    private Combination[][] combinations;
    private string[] typenames;
    private getString[] tostrings;

    public Environment() { ... } //adds one 'Null' type at index 0 by default
    public void AddType(string name, getString tostring, Combination[] combos) { ... }

    public Value Combine(Value A, Value B)
    {
        return combinations[A.index][B.index](A, B);
    }
    public string getStringValue(Value A)
    {
        return tostrings[A.index](A);
    }
    public string getTypeString(Value A)
    {
        return typenames[A.index];
    }
}

class Container
{
    public string contents
    {
        get
        {
            return data.text;
        }
    }
    public string contentType
    {
        get
        {
            return data.type;
        }
    }

    private Value data;

    public Container(Value val)
    {
        data = val;
    }

    public Container CombineContents(Container B)
    {
        return new Container(data.Combine(B.data))
    }
}

struct Value
{
    public string type
    {
        get 
        {
            return environment.getTypeString(this);
        }
    }
    public string text
    {
        get 
        {
            return environment.getStringValue(this);
        }
    }

    public readonly int type;
    public readonly byte[] raw;
    public readonly Environment environment;
    public Value(int t, byte[] bin, Environment env)
    {
        type = t;
        raw = bin;
        environment = env;
    }

   public Value Combine(Value B)
   {
       return environment.Combine(this, B)
   }
}

The reason for this structure is that Containers can have Values of various types, which combine with each other in user-defined ways according to the current Environment (which, like Container and Value, is differently named so as to avoid conflicting with the System.Environment class in my actual code- I used the name here to concisely imply its function). I cannot get around the problem with subclasses of Value and generic Containers since values of different types still need to be combinable, and neither Container nor the base Value class can know what type of Value combination should return.

It doesn't seem possible to define the Environment class in a global way, as the existing System.Environment class doesn't seem to allow storing delegates as user variables, and giving it a static method returning an instance of itself would render it unmodifiable*, and would require a new instance of the class to be created every time I want to do anything with Values, which seems like it should be a huge performance hit.

This causes two problems for me:

  • There is an extra reference padding out all my Values. Values are variable in size, but raw is almost always 8 bits or less, so the difference is significant, especially since in actual implementations it will be fairly common to have several million Values and Containers in memory at once.
  • It is impossible to define a proper 'null' Value, as a Value must have an Environment in it and the Environment must be mutable. This in turn means that Container constructors that do not take a Value as an argument are much more convoluted.

The only other way around this I can think of would be to have a wrapper class (either an extension of Environment or something with an environment as a parameter) which is required in order to work with Containers or Values, which has all extant Containers and Values as members. This would solve the 'null' problem and neaten up the Value class a bit, but adds a huge amount of overhead as described and makes for a really convoluted interface for the end user. Those problems are, with a good deal of work and some changes in program flow, solvable as well, but by that point I'm pretty much writing another programming language which is far more than I should need.

Is there any other workaround for this that I'm missing, or am I mistaken about any of my disqualifying factors above? The only thing I can think of is that the performance hit from the static implementation might be smaller than I think it would be due to cacheing (I cannot perform realistic benchmarking unfortunately- there are too many variables in how this could be used).


*Note that an environment doesn't strictly speaking need to be modifiable- there would be no problem, technically, for example, with something like

class Environment
{
    private Combination[][] combinations;
    private string[] typenames;
    private getString[] tostrings;

    public Environment(Combination[][] combos, string[] tnames, getString[] getstrings)
    {
        combinations = combos;
        typenames = tnames;
        tostrings = getstrings;
    }
}

except that this would be much more awkward for the end user, and doesn't actually fix any of the problems I've noted above.

like image 826
P... Avatar asked Dec 19 '25 12:12

P...


1 Answers

I had a lot of trouble trying to understand exactly what you were trying to achieve here! So apologies if I'm off the mark. Here is a singleton based example that, if I understand the problem correctly, may help you:

public class CombinationDefinition
{
    public string Name;
    public getString GetString;
    public Combination[] Combinations;
}

public static class CurrentEnvironment
{
    public static CombinationDefinition[] Combinations = new CombinationDefinition[0];
    public static Environment Instance { get { return _instance.Value; } }

    static ThreadLocal<Environment> _instance = new ThreadLocal<Environment>(() =>
        {
            Environment environment = new Environment();

            foreach (var combination in Combinations)
                environment.AddType(combination.Name, combination.GetString, combination.Combinations);

            return environment;
        });

    public static Value CreateValue(int t, byte[] bin)
    {
        return new Value(t, bin, Instance);
    }
}

Which can be used as:

CurrentEnvironment.Combinations = new CombinationDefinition[]
{
    new CombinationDefinition() { Name = "Combination1", GetString = null, Combinations = null },
    new CombinationDefinition() { Name = "Combination2", GetString = null, Combinations = null },
};

Value value = CurrentEnvironment.CreateValue(123, null);
string stringValue = CurrentEnvironment.Instance.getStringValue(value);

Important to note - CurrentEnvironment.Combinations must be set before the Environment is used for the first time as accessing the Instance property for the first time will cause the Environment to be instantiated by its ThreadLocal container. This instantiation uses the values in Combinationsto use the existing AddType method to populate the Environment.

like image 171
TVOHM Avatar answered Dec 21 '25 01:12

TVOHM