防止对象重复不变

时间:2020-02-03 12:54:14

标签: c# list immutability

对于游戏,我正在创建许多确定NPC例程的不可变对象。 一个节点对象更改x和z的值,一个对象更改y的值,一个对象更改角度,依此类推。我为他们开设了十二个不同的班级。 这些对象是NPC所走路径的网络。 这些对象还捆绑成一个更大的对象,即节点。该节点在一段时间内一直是NPC的操作节点,直到分配下一个节点为止。

我希望最后有数千个节点。但是我确信这些节点中的对象通常是其他某些节点中对象的重复。

要使该系统在内存中的占用空间保持较低,我希望每帧将y值更改5的对象与在其他每个节点中具有将y值更改5的对象相同的对象。希望每个节点都引用相同的对象。

我试图通过将这些对象的构造函数设置为private并使用一种方法(如果参数是唯一的)返回一个新对象,或者使用一个在创建过程中具有相同参数的现有对象的方法来归档该文件。

当我为每个要防止具有相同对象的类提供LinkedList时,我已经成功实现了。这是一个看起来像这样的示例:

private class NodeEndTime : NodeEnd
{
   //attributes
   private readonly int dayTime;

   //constructor
   private NodeEndTime(int dayTime) { this.dayTime = dayTime; }

   //methods (only the one relevant for the question)
   public static NodeEndTime Constructor(int dayTime, LinkedList<NodeEndTime> list)
   {
      foreach (NodeEndTime node in list)
      {
         if (node.dayTime == dayTime) return node;
      }
      NodeEndTime fresh = new NodeEndTime(dayTime);
      list.AddLast(fresh);
      return fresh;
   }
}

我对此不满意的是,我需要每个类都有一个唯一的LinkedList。我想要的是所有对象的列表。但是我不知道该怎么做。 一个想法是拥有一个唯一列表列表,该列表将在运行时将所需列表创建为属性(如果尚不存在),并完成自动访问正确列表的工作。但是我也不知道该怎么做。

什么是干净的解决方案?我怀疑仿制药会有所帮助,但我没有足够的经验来了解适合他们的方法。我还需要注意,这些列表仅在加载过程中存在。创建的对象以NPC可用的不同结构存在。因此,将它们包括在类中不是一个选择。除非我可以在加载后以某种方式删除它们。

2 个答案:

答案 0 :(得分:1)

要执行此操作,您需要一个带有私有构造函数的类,该类可以创建某种键来唯一标识对象。然后可以将其存储在Dictionary中,并在每次尝试创建新实例时进行查询。如果已经存在具有相同键的实例,则将其返回,否则创建一个新实例并将其添加到字典中。像这样,您调用Get,将获得一个现有副本或一个新实例。

public class NoDupeObject
{
    private static readonly Dictionary<string, NoDupeObject> _keyLookup = new Dictionary<string, NoDupeObject>();

    private NoDupeObject(string p1, int p2, DateTime p3) 
    {
        P1 = p1;
        P2 = p2;
        P3 = p3;
    }

    public string P1 { get; }
    public int P2 { get; }
    public DateTime P3 { get; }

    public static NoDupeObject Get(string p1, int p2, DateTime p3) 
    {
        var key = string.Join("-", p1, p2, p3);
        if (!_keyLookup.TryGetValue(key, out var value))
        {
            value = new NoDupeObject(p1, p2, p3);
            _keyLookup.Add(key, value);
        }

        return value;
    }
}

如下所示使用。该方法确认仅创建了两个对象,并且o1 == o3

    public static void Test()
    {
        // Creates an object
        var o1 = NoDupeObject.Get("O1", 10, new DateTime(2020, 01, 01));
        // Creates another object
        var o2 = NoDupeObject.Get("O2", 10, new DateTime(2020, 01, 01));
        // Gets a copy of the first object
        var o3 = NoDupeObject.Get("O1", 10, new DateTime(2020, 01, 01));
        Debug.Assert(o1 != o2);
        Debug.Assert(o1 == o3);
    }

在另一个主题上。性能通常在游戏中至关重要,并且在对象创建时在链表中进行迭代会非常慢。

答案 1 :(得分:1)

如果您可以使要缓存的所有类型都实现IEquatable,则可以相对简单地做到这一点(只要这些类型不是一次性的,这会使事情复杂化)。

首先,为要缓存的所有类型实现IEquatable<T>。这将使您可以将所有项目存储在一个HashSet中。

然后创建一个HashSet<object>来包含所有缓存的项目:

static readonly HashSet<object> _lookup = new HashSet<object>();

然后,当您要获取新对象时,请创建该对象(这样便可以将其用作键),然后在HashSet中进行查找。如果找到,则返回找到的项目。否则,将其添加到HashSet并返回您添加的项目。

请注意,除非您正在使用Resharper之类的东西,它将为您生成代码,否则实现IEquatable<T>是相当愚蠢的。 (在下面的示例中,我已将Resharper用于此目的。)

最好通过一个示例对此进行解释:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var a1 = GetFirstType(42);
        var b1 = GetFirstType(43);
        var c1 = GetFirstType(42);

        if (object.ReferenceEquals(a1, b1))
            Console.WriteLine("a1 == b1");
        else
            Console.WriteLine("a1 != b1");

        if (object.ReferenceEquals(b1, c1))
            Console.WriteLine("b1 == c1");
        else
            Console.WriteLine("b1 != c1");

        if (object.ReferenceEquals(a1, c1))
            Console.WriteLine("a1 == c1");
        else
            Console.WriteLine("a1 != c1");

        Console.WriteLine();

        var a2 = GetSecondType("42");
        var b2 = GetSecondType("43");
        var c2 = GetSecondType("42");

        if (object.ReferenceEquals(a2, b2))
            Console.WriteLine("a2 == b2");
        else
            Console.WriteLine("a2 != b2");

        if (object.ReferenceEquals(b2, c2))
            Console.WriteLine("b2 == c2");
        else
            Console.WriteLine("b2 != c2");

        if (object.ReferenceEquals(a2, c2))
            Console.WriteLine("a2 == c2");
        else
            Console.WriteLine("a2 != c2");
    }

    public static FirstType GetFirstType(int value)
    {
        var item = new FirstType(value);

        if (_lookup.TryGetValue(item, out var found))
            return (FirstType) found;

        _lookup.Add(item);
        return item;
    }

    public static SecondType GetSecondType(string value)
    {
        var item = new SecondType(value);

        if (_lookup.TryGetValue(item, out var found))
            return (SecondType)found;

        _lookup.Add(item);
        return item;
    }

    static HashSet<object> _lookup = new HashSet<object>();
}

public sealed class FirstType: IEquatable<FirstType>
{
    public FirstType(int value)
    {
        IntValue = value;
    }

    public int IntValue { get; }

    public bool Equals(FirstType? other)
    {
        if (ReferenceEquals(null, other))
            return false;

        if (ReferenceEquals(this, other))
            return true;

        return IntValue == other.IntValue;
    }

    public override bool Equals(object? obj)
    {
        return ReferenceEquals(this, obj) || obj is FirstType other && Equals(other);
    }

    public override int GetHashCode()
    {
        return IntValue;
    }

    public static bool operator ==(FirstType? left, FirstType? right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(FirstType? left, FirstType? right)
    {
        return !Equals(left, right);
    }
}

sealed class SecondType: IEquatable<SecondType>
{
    public SecondType(string value)
    {
        StringValue = value;
    }

    public string StringValue { get; }

    public bool Equals(SecondType? other)
    {
        if (ReferenceEquals(null, other))
            return false;

        if (ReferenceEquals(this, other))
            return true;

        return StringValue == other.StringValue;
    }

    public override bool Equals(object? obj)
    {
        return ReferenceEquals(this, obj) || obj is SecondType other && Equals(other);
    }

    public override int GetHashCode()
    {
        return StringValue.GetHashCode();
    }

    public static bool operator ==(SecondType? left, SecondType? right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(SecondType? left, SecondType? right)
    {
        return !Equals(left, right);
    }
}

在此示例中要注意的重要一点是,我为FirstType然后为SecondType创建了两个具有相同值(42)和一个具有不同值(43)的实例。然后,我比较从GetFirstType()返回的对象的引用,然后再比较GetSecondType(),以证明如果基础值相同,则返回相同的实例。

完成初始化后,可以将HashSet设置为null,以便GC可以清理所有未使用的实例。

(注意:上面的代码假定您正在使用nullable类型-如果不使用,请从类型声明中删除所有?。)