Предотвращение дублирования неизменных объектов - PullRequest
0 голосов
/ 03 февраля 2020

Для игры я создаю множество неизменяемых объектов, которые определяют рутины NPC. Один объект узла изменяет значения x и z, один объект - значение y, один объект - угол и так далее. У меня есть дюжина разных классов для них. Эти объекты представляют собой совокупность путей, по которым идет NP C. Эти объекты также объединяются в более крупный объект, который является узлом. Этот узел манипулирует NP C в течение периода времени, пока не будет назначен следующий узел.

Я ожидаю, что в конце будут тысячи узлов. Но я уверен, что объекты в этих узлах часто являются дубликатами объектов в некоторых других узлах.

Чтобы сохранить объем этой системы в моей памяти на низком уровне, я хочу, чтобы объект, который изменяет значение y на 5, frame - это тот же объект в каждом другом узле, у которого есть этот объект, который меняет значение 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 ]

1 голос
/ 03 февраля 2020

Если вы можете сделать так, чтобы все типы, которые вы хотите кэшировать, реализовали IEquatable, вы можете сделать это относительно просто (если типы НЕ являются одноразовыми; это значительно усложнит ситуацию).

Во-первых , внедрите IEquatable<T> для всех типов, которые вы хотите кэшировать. Это позволит вам хранить все элементы в одном HashSet.

Затем создайте HashSet<object> для хранения всех кэшированных элементов:

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

Затем, когда вы хотите получить новый объект, создайте объект (чтобы вы могли использовать его в качестве ключа), затем найдите его в HashSet. Если он найден, верните найденный предмет; в противном случае добавьте его в HashSet и верните добавленный вами элемент.

Обратите внимание, что реализовать IEquatable<T> довольно сложно, если вы не используете что-то вроде Resharper, которое сгенерирует код для вас. (Я использовал 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);
    }
}

В этом примере важно отметить, что я создаю два экземпляра с одинаковым значением (42) и один с другим значением (43) для FirstType и затем для SecondType. Затем я сравниваю ссылки на объекты, возвращенные из GetFirstType(), а затем GetSecondType(), чтобы доказать, что тот же экземпляр возвращается, если базовое значение совпадает.

Как только вы закончили инициализацию, вы можете установить от HashSet до null, чтобы G C мог очистить любые неиспользуемые экземпляры.

(Примечание. Приведенный выше код предполагает, что вы используете типы nullable - если это не так, удалите все ? s из объявлений типов.)

1 голос
/ 03 февраля 2020

Для этого вам нужен класс с закрытым конструктором, который создает какой-то ключ для уникальной идентификации объекта. Затем его можно сохранить в словаре и искать каждый раз, когда вы пытаетесь создать новый экземпляр. Если экземпляр с таким же ключом уже существует, вы возвращаете его, в противном случае вы создаете новый экземпляр и добавляете его в словарь. С чем-то вроде этого вы вызываете 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);
    }

По отдельной теме. Производительность часто имеет решающее значение в играх, и перебор связанного списка в момент создания объекта будет невероятно медленным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...