2

Is it possible to iterate through a several generic instances and do something with them?

public class Dynamic<T>
{
    public T value;
    public T defaultVal;

    public void SetDefaultValue()
    {
        value = defaultVal;
    }
}

Is it even possible to make it look something like this:

List<Dynamic<T>> dynamics = new List<Dynamics<T>>();

dynamics.Add(new Dynamic<float>());
dynamics.Add(new Dynamic<Vector2>());
///...

foreach (var item in dynamics)
{
    item.SetDefaultValue();
}

And yeah, its probably useless at all since I have to manually insert these values in that list, but I really want to know if its even possible.

bleat interteiment
  • 1,429
  • 2
  • 10
  • 15
  • 1
    It's not convenient to do that with without changing `Dynamic` a little. What version of c# does Unity support these days? Does it support all the functionality of [c# 4.0](https://learn.microsoft.com/en-us/archive/msdn-magazine/2010/july/csharp-4-0-new-csharp-features-in-the-net-framework-4)? – dbc Mar 19 '22 at 14:12
  • Is c# 4.0 and .NET 4.0 the same thing right? If so, then it does, but I'm using 2.0 and never had any reason to switch – bleat interteiment Mar 19 '22 at 14:17
  • 1
    C# 4.0 was released with .NET 4, see [What are the correct version numbers for C#?](https://stackoverflow.com/q/247621/3744182). c# 4.0 supports [generic covariance](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance) which might allow you to do what you want more elegantly, but if you're working in c# 2.0 that's not available. – dbc Mar 19 '22 at 14:19
  • Actually, on further thought, generic covariance isn't really helpful here because your generic parameter `T` is sometimes a `struct`. – dbc Mar 19 '22 at 15:23
  • 1
    [C# version that Unity supports](https://docs.unity3d.com/Manual/CSharpCompiler.html) – Robert Harvey Mar 19 '22 at 15:25

2 Answers2

2

With the data model you have, in c# 2.0 there is no way to do what you want other than by declaring dynamics as a List<object> and using reflection:

List<object> dynamics = new List<object>();

dynamics.Add(new Dynamic<float>());
dynamics.Add(new Dynamic<Vector2>());

foreach (var item in dynamics)
{
    item.GetType().GetMethod("SetDefaultValue").Invoke(item, null);
}

Not really recommended, since the performance will be poor, and you lose compile-time checking for correctness.

Instead, consider introducing a non-generic interface that allows accessing required members of Dynamic<T> in a non-generic manner:

public interface IDynamic
{
    // Provide read-only access to the Value and DefaultValue as objects:
    object Value { get; }
    object DefaultValue { get; } 
    // Set the value to the default value:
    void SetDefaultValue();
}

public class Dynamic<T> : IDynamic
{
    public T value;
    public T defaultVal;

    public void SetDefaultValue()
    {
        value = defaultVal;
    }

    // Use explicit interface implementation because we don't want people to use the non-generic properties when working directly with a specific Dynamic<T>:
    object IDynamic.Value { get { return value; } } // Note that if T is a struct the value will get boxed
    object IDynamic.DefaultValue { get { return defaultVal; } } // Note that if T is a struct the value will get boxed
}

Now you will be able to do:

List<IDynamic> dynamics = new List<IDynamic>();

dynamics.Add(new Dynamic<float>());
dynamics.Add(new Dynamic<Vector2>());

foreach (var item in dynamics)
{
    item.SetDefaultValue();
}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    Unity [supports C# 7 and most of C# 8](https://docs.unity3d.com/Manual/CSharpCompiler.html). – Robert Harvey Mar 19 '22 at 15:27
  • Right, but apparently (according to comments) the querent hasn't upgraded and is still working in c# 2.0. – dbc Mar 19 '22 at 15:28
  • Thanks a lot! I don't know how I didn't think of implementing an interface on top of the class. Actually, got to say that I don't even need values to be sitting inside of an interface as I never refer to them directly through a list. "SetDefaultValue" method is well enough for my needs. And maybe I misunderstand something about .NET versions in Unity but these solutions work even when I set "API Compatibility Level" to ".NET Standard 2.0". – bleat interteiment Mar 19 '22 at 18:42
  • @bleatinterteiment - *these solutions work even when I set "API Compatibility Level" to ".NET Standard 2.0".* -- yes, I'm just using older language features here that should work just fine on later versions. I included non-generic access to the values to show how it could be done; remove if not needed. – dbc Mar 19 '22 at 19:30
1

Extending dbc's answer, to have the properties of your class stored in a list to change them at once and by reference you need to wrap them into a class, for this case Dynamic<whatever>, because if you have primitive/value types, those will be copied into the list, and even the property values are changed looping through the list you would be changing the list copies and not the class properties themselves. Like so:

using System;
using System.Collections.Generic;
using UnityEngine;

public class GenericTrial2 : MonoBehaviour {
    Dynamic<float> vertical = new Dynamic<float> { value = 17, defaultVal = 1 };
    Dynamic<Vector2> run = new Dynamic<Vector2> { value = new Vector2(17, 17), defaultVal = Vector2.one };
    Dynamic<Quaternion> rotation= new Dynamic<Quaternion> { value = new Quaternion(4,4,4,4), defaultVal = Quaternion.identity };
    List<IDynamic> dynamics;

    private void Start() {
        dynamics = new List<IDynamic>();
        dynamics.Add(vertical);
        dynamics.Add(run);
        dynamics.Add(rotation);
        dynamics.ForEach(prop => { Debug.Log(prop.Value.ToString()); });
        Debug.Log($"initValues: vertical: {vertical.value.ToString()}, " +
            $"run: {run.value.ToString()}, rot: {rotation.value.ToString()} ");
    }

    void resetProps() {
        foreach (var item in dynamics) {
            item.SetDefaultValue();
        }
        dynamics.ForEach(prop => { Debug.Log(prop.Value.ToString()); });
    }

    private void Update() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            resetProps();
            Debug.Log($"after reset: vertical: {vertical.value.ToString()}, " +
                $"run: {run.value.ToString()}, rot: {rotation.value.ToString()} ");
        }
    }
}
public interface IDynamic {
    // Provide read-only access to the Value and DefaultValue as objects:
    object Value { get; }
    object DefaultValue { get; }
    // Set the value to the default value:
    void SetDefaultValue();
}

public class Dynamic<T> : IDynamic {
    public T value;
    public T defaultVal;

    public void SetDefaultValue() {
        value = defaultVal;
    }

    // Use explicit interface implementation because we don't want people to use the non-generic properties when working directly with a specific Dynamic<T>:
    object IDynamic.Value { get { return value; } } // Note that if T is a struct the value will get boxed
    object IDynamic.DefaultValue { get { return defaultVal; } } // Note that if T is a struct the value will get boxed
}

Output:

initValues: vertical: 17, run: (17.0, 17.0), rot: (4.0, 4.0, 4.0, 4.0)   
after reset: vertical: 1, run: (1.0, 1.0), rot: (0.0, 0.0, 0.0, 1.0) 
rustyBucketBay
  • 4,320
  • 3
  • 17
  • 47