Delegates to generic operations where the generic

2019-03-04 17:11发布

问题:

Suppose I have the following code.

static class Store<T> {
    public static T A;
    public static T B;
    public static T C;
}

public static class Store {
    public static Value A = new Value(<T>(v) => Store<T>.A = v); //just an example of what I want
    public static Value B = new Value(<T>(v) => Store<T>.B = v); //just an example of what I want
    public static Value C = new Value(SetC<T>);  //just an example of what I want

    public static void SetA<T>(T value) { Store<T>.A = value; }
    public static void SetB<T>(T value) { Store<T>.B = value; }
    public static void SetC<T>(T value) { Store<T>.C = value; }
}

public class Value {
    Action<T><T> _valueChanger; //just an example of what I want
    public Value(Action<T><T> valueChanger) { //just an example of what I want
        _valueChanger = valueChanger;
    }

    public void SetValue<T> (T value) {
        _valueChanger<T>(value); //just an example of what I want
    }
}

I want to write Store.A.SetValue(42) so that the value is saved to Store<int>.A. What can I write instead of the lines marked by "just an example of what I want" to make that happen? (I want to explore a solution that doesn't involve dictionaries or something similar)

Rephrasing the question: I want to modify class Value (define some fields, write a constructor and write the method Value.SetValue(T value) ), then construct three different variables of type Value (A, B, C) in such a way that when I call Store.A.SetValue(42) the value Store<int>.A is changed to 42.

Another variation of the classes:

static class Holder<T> {
    T Value { get; set; }
}

static class Store2<T> {
    public static Holder<T> A = new Holder<T>();
    public static Holder<T> B = new Holder<T>();
    public static Holder<T> C = new Holder<T>();
}

public static class Store2 {
    public static Value A = new Value2(Store2<>.A); //just an example of what I want
    public static Value B = new Value2(Store2<>.B); //passing non-specific generic expression
    public static Value C = new Value3({TFree}() => Store2<TFree>.C); //just an example of what I want
}

public class Value2 { //Non-generic class!
    Holder{TFree}<TFree> _holder; //just an example of what I want
    public Value(Holder{TFree}<TFree> holder) { //just an example of what I want
        _holder = holder;
    }

    public void SetValue<T> (T value) {
        _holder{T}.Value = value; //just an example of what I want
    }
}

public class Value3 { //Non-generic class! (Another variation)
    Func{TFree}<Holder<TFree>> _holderFactory; //just an example of what I want

    public Value(Func{TFree}<Holder<TFree>> holderFactory) { //just an example of what I want
        _holderFactory = holderFactory;
    }

    public void SetValue<T> (T value) {
        Holder<T> holder = _holderFactory{T}(); //just an example of what I want
        holder.Value = value; 
    }
}

Solution: An easy reflection-free and collection-free solution was found using the answers to another question ( Emulating delegates with free generic type parameters in C# and Emulating delegates with free generic type parameters in C#). The solution is Delegates to generic operations where the generic type is unknown. How to create something like that?.

回答1:

Use an array to store the values and access them through a property using an index

public static class Store<T>
{
    public static readonly T[] Values = new T[3];
    public static T A { get { return Values[0]; } set { Values[0] = value; } }
    public static T B { get { return Values[1]; } set { Values[1] = value; } }
    public static T C { get { return Values[2]; } set { Values[2] = value; } }
}

public static class Store
{
    public static readonly Value A = new Value(0);
    public static readonly Value B = new Value(1);
    public static readonly Value C = new Value(2);
}

public class Value
{
    private int _index;

    public Value(int index)
    {
        _index = index;
    }

    public void SetValue<T>(T value)
    {
        Store<T>.Values[_index] = value;
    }

    public T GetValue<T>()
    {
        return Store<T>.Values[_index];
    }
}

Since the constructor of Value is not aware of any generic type parameter, you cannot have any reference to a specific Store<T>.


UPDATE

Be aware of the fact that a copy of Store<T> will be created for every distinct type argument that you supplied for T. See this example

Store.A.SetValue(42);
Store.A.SetValue("Douglas Adams");
Store.A.SetValue(new DirectoryInfo(@"C:\"));
Store.A.SetValue(new List<int>());

var x1 = Store.A.GetValue<int>();           // --> 42
var x2 = Store.A.GetValue<string>();        // --> "Douglas Adams"
var x3 = Store.A.GetValue<DirectoryInfo>(); // --> DirectoryInfo{ C:\ }
var x4 = Store.A.GetValue<List<int>>();     // --> List<int>{ Count = 0 }

By using the debugger, you will see that four different values are stored in A at the same time! Of cause these are four differents A's that exist in four diffferent Store<T>.



回答2:

The problem turned out to be solvable. Mike-z gave me a nearly right solution for the delegate-to-generic-method problem ( Emulating delegates with free generic type parameters in C#) which I modified to be a full solution: ( Emulating delegates with free generic type parameters in C#).

The solution this question becomes easy too. Interfaces can contain generic methods and we can use the interface-valued variables to store links to generic methods without specifying concrete type arguments. The following code utilizes the Store<T> class without modifications and uses the ISetter interface and ASetter/BSetter/CSetter "closures" to hold references to different generic members. The Value class stores the references in a ISetter-typed variable and uses the generic member which the _setter links to once the type argument T becomes available.

public interface ISetter {
    void SetValue<T>(T value);
}

public static class Store {
    public static Value A = new Value(new ASetter());
    public static Value B = new Value(new BSetter());
    public static Value C = new Value(new CSetter());

    class ASetter : ISetter {
        public void SetValue<T>(T value) { Store<T>.A = value; }
    }
    class BSetter : ISetter {
        public void SetValue<T>(T value) { Store<T>.B = value; }
    }
    class CSetter : ISetter {
        public void SetValue<T>(T value) { Store<T>.C = value; }
    }
}

public class Value {
    ISetter _setter;

    public Value(ISetter setter) {
        _setter = setter;
    }

    public void SetValue<T> (T value) {
        _setter.SetValue<T>(value);
    }
}