Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
98 views
in Technique[技术] by (71.8m points)

c# - Returning references from Generic Arrays

I've got a class which holds a list of ComponentBase. Component sub classes ComponentBase.

    public abstract class ComponentBase 
    {
        public Type Type;
        public dynamic Value;
        public uint EntityID;
    }

    public class Component<T> : ComponentBase
    {
        public new Type Type;
        public new T Value;
        public new uint EntityID;
    }

I need a way to retrieve a ref to a Component<T>.Value in the GetComponentOnEntity method and I can't seem to find it. Im not very good at generics so I'll put this question to the pros.

    public class ComponentDatabase
    {
        ComponentBase[] components;

        private int componentIndex;

        public T AddComponentToEntity<T>(T component, Entity entity) where T : new()
        {
            var x = component != null ? component : new T();
            var comp = new Component<T> { EntityID = entity.Id, Type = typeof(T), Value = x };
            components[componentIndex++] = comp;
            return x;
        }

        public ref T GetComponentOnEntity<T>(Entity entity) where T : new()
        {
            return ref components.Where(x => x.EntityID == entity.Id && x.Type == typeof(T)).First().Value;
        }
    }

Ive had a long night and I feel like there is a simple solution but I really can't find it. Any help would be greatly appreciated.

question from:https://stackoverflow.com/questions/65890050/returning-references-from-generic-arrays

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Fundamentally, the issue with your code is that you are trying to return a reference to the wrong member of the object. You are returning the member with the dynamic type, when you actually need the member having the type T.

Your original example is equivalent to this simpler one:

class Base
{
    public int Id;
    public dynamic Value;

    public Base(int id, dynamic value)
    {
        Id = id;
        Value = value;
    }

    public override string ToString() => $"{{Id: {Id}, Value: {Value}}}";
}

class Derived<T> : Base
{
    public new int Id;
    public new T Value;

    public Derived(int id, T value) : base(id, value) { }
}

static void Main(string[] args)
{
    Derived<string>[] array = new[]
    {
        new Derived<string>(0, "Zero"),
        new Derived<string>(1, "One"),
        new Derived<string>(2, "Two"),
    };

    ref string valueRef = ref GetElement<string>(array, 1);

    valueRef = "Three";
    WriteLine(string.Join<Base>(Environment.NewLine, array));
}

private static ref T GetElement<T>(Base[] array, int id)
{
    // error CS8151: The return expression must be of type 'T' because this method returns by reference
    return ref array.Where(x => x.Id == id).First().Value;
}

The problem is simply that you are trying to return the base class field, which has the type dynamic rather than T.

To fix that, you need to cast the object so that you get the field from the derived type instead:

private static ref T GetElement<T>(Base[] array, int id)
{
    return ref ((Derived<T>)array.Where(x => x.Id == id).First()).Value;
}

Or, in your original example:

public ref T GetComponentOnEntity<T>(Entity entity) where T : new()
{
    return ref ((Component<T>)components.Where(x => x.EntityID == entity.Id && x.Type == typeof(T)).First()).Value;
}

Now, all that said: I urge you to rethink the design. Perhaps you've omitted some code that initializes the base Value field identically to the derived Value field. But even if so (and that's wasteful enough), there's nothing that prevents those fields from being assigned differently later on. It's not just wasteful, it's also likely to lead to bugs to have an object that has two copies of every value in it, especially when those values are not read-only.

Member hiding (i.e. using the new keyword as you have in your data structures) should be avoided at all costs. It only leads to confusion and will almost certainly result in any number of hard-to-find, hard-to-fix bugs later down the road. The issue you've run into here is just the tip of the iceberg, but is a good example of how confusing things can be when an object has two different members that have the same name.

In addition, ref return values should be used very sparingly, and only when absolutely necessary (e.g. a critical performance issue that can be fixed only using ref return). There's context missing from your question of course, and so maybe you do have a really good reason for using that feature here. But just based on the names in the code that's present, it seems likely to me that you could get the code to work just fine without either of the oddities in the code now (the member hiding and the ref return values).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...