Доступ частного установщика не запрещен

#c#

#c#

Вопрос:

Постановка проблемы

Существует пользовательский векторный класс:

 namespace StackoverflowQuestion1
{
    public class MyVector
    {
        public float x;
        public float y;
        public float z;

        public MyVector(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }
}
 

Существует интерфейс для всего, что является подвижным, что означает, что позиции могут меняться:

 namespace StackoverflowQuestion1
{
    public interface IMovable
    {
        public string Name { get; }
        public MyVector Position { get; }
    }
}
 

Мебель подвижна, поэтому она реализует соответствующий интерфейс:

 namespace StackoverflowQuestion1
{
    public class Furniture : IMovable
    {
        public string Name { get; private set; }
        public MyVector Position { get; private set; }

        public Furniture(string name, float x, float y, float z)
        {
            this.Name = name;
            this.Position = new MyVector(x, y, z);
        }
    }
}
 

Как и ожидалось, доступ к частному получателю имени невозможен. Доступ к частному установщику позиции также не работает, как и ожидалось. Однако доступ к полям позиции возможен, поскольку они являются общедоступными.

 using StackoverflowQuestion1;

class Program
{
    static void Main(string[] args)
    {
        Furniture F = new Furniture("Chair", 1f, 2f, 3f);
        F.Name = "Office chair"; // doesn't work, as expected
        F.Position = new MyVector(5f, 6f, 7f); // doesn't work, as expected
        F.Position.x = 5f; // works, unfortunately
        F.Position.y = 6f; // works, unfortunately
        F.Position.z = 7f; // works, unfortunately
    }
}
 

Вопрос

Как сделать невозможным изменение положения мебели, не делая координаты MyVector частными и, следовательно, недоступными? Я хочу иметь инкапсуляцию, позволяя только Furniture членам получать доступ к позиции, но MyVector станет бесполезным в других местах, если его значения нельзя изменить.

Комментарии:

1. Рассматривали ли вы возможность создания MyVector структуры (или использования существующей векторной структуры)?

2. Вы можете создать пользовательский геттер для Position , в Furniture котором возвращается копия MyVector объекта, поэтому любые изменения, внесенные в эту копию, не вносятся в оригинал. Но если ваше требование заключается в том, что поля MyVector должны быть общедоступными, тогда они являются общедоступными и, следовательно, доступными.

3. Сделайте вектор неизменяемой структурой (или структурой записи) и используйте выражение with (ссылка на C #) , чтобы получить измененную копию.

Ответ №1:

Здесь нужно сделать несколько замечаний:

  1. По замыслу вы решили создать поля public , что означает, что они легко доступны из других классов. Они не являются частными, что и следует из названия. Чтобы заставить их быть доступными только для чтения, используйте readonly ключевое слово
     public class MyVector
    {
        public readonly float x;
        public readonly float y;
        public readonly float z;
    
        public MyVector(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }
     
  2. Обычно вы не предоставляете поля, а вместо этого используете свойства только с определенными геттерами.
     public class MyVector
    {
        private readonly float x;
        private readonly float y;
        private readonly float z;
    
        public MyVector(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    
        public float X { get => x; }
        public float Y { get => y; }
        public float Z { get => z; }
    }
     
  3. Кроме того, вы можете упростить задачу, используя автоматические свойства
     public class MyVector
    {
    
        public MyVector(float x, float y, float z)
        {
            this.X = x;
            this.Y = y;
            this.Z = z;
        }
    
        public float X { get; }
        public float Y { get; }
        public float Z { get; }
    }
     
  4. Наконец, это рекомендуется для семантики значений, где (x, y, z) всегда будут идти вместе, чтобы использовать struct объявления.
     public readonly struct MyVector
    {
    
        public MyVector(float x, float y, float z)
        {
            this.X = x;
            this.Y = y;
            this.Z = z;
        }
    
        public float X { get; }
        public float Y { get; }
        public float Z { get; }
    }
     

В качестве примечания, если вы попытаетесь изменить содержимое структуры, предоставляемой свойством, C # будет жаловаться.

Рассмотрим этот код

 public struct MyVector
{
    public float x;
    public float y;
    public float z;

    public MyVector(float x, float y, float z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

public class Movable
{
    public Movable(MyVector position)
    {
        Position = position;
    }

    public MyVector Position { get; }
}
 

scr1

Таким образом, даже если дизайн позволяет изменять содержимое MyVector (изменять), компилятор остановит вас. Это связано с тем, что с struct типами у вас везде есть локальные копии данных, и при записи Position.x = 10f вы бы изменили локальную копию Position , которая существует в области, где это вызывается, а не изменили исходные данные.

В вопросе MyVector указан класс, который, таким Position.x = 10f образом, изменяет исходные данные, и, как указано, это нежелательное поведение, поэтому выполните описанные выше действия, чтобы запретить это поведение.


Чтобы обеспечить MyVector хорошую работу с другими классами, я часто добавляю следующие функциональные возможности к таким дефляциям. Я добавляю поддержку .ToString() с форматированием и добавляю поддержку .Equals() == для структур), чтобы можно было писать такой код:

 static void Main(string[] args)
{
    var pos = new MyVector(1f, 1/2f, 1/3f);
    var m = new Movable(pos);

    if (m.Position == pos)
    {
        Console.WriteLine($"{m.Position:f2}");
        // (1.00,0.50,0.33)
    }
}
 

Обратите внимание на форматирование с 2 десятичными знаками и проверку равенства.

вот полный код, который позволяет это для вашей справки

MyVector.cs

 public readonly struct MyVector : IEquatable<MyVector>, IFormattable
{
    public MyVector(float x, float y, float z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public float X { get; }
    public float Y { get;  }
    public float Z { get;  }


    #region IEquatable Members
    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(MyVector)</code></returns>
    public override bool Equals(object obj)
    {
        if (obj is MyVector other)
        {
            return Equals(other);
        }
        return false;
    }

    public static bool operator ==(MyVector target, MyVector other) { return target.Equals(other); }
    public static bool operator !=(MyVector target, MyVector other) { return !(target == other); }


    /// <summary>
    /// Checks for equality among <see cref="MyVector"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="MyVector"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public bool Equals(MyVector other)
    {
        return X.Equals(other.X)
            amp;amp; Y.Equals(other.Y)
            amp;amp; Z.Equals(other.Z);
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="MyVector"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
        unchecked
        {
            int hc = -1817952719;
            hc = (-1521134295) * hc   X.GetHashCode();
            hc = (-1521134295) * hc   Y.GetHashCode();
            hc = (-1521134295) * hc   Z.GetHashCode();
            return hc;
        }
    }

    #endregion

    #region Formatting
    public override string ToString() => ToString("g");
    public string ToString(string formatting) => ToString(formatting, null);
    public string ToString(string format, IFormatProvider provider)
    {
        return $"({X.ToString(format, provider)},{Y.ToString(format, provider)},{Z.ToString(format, provider)})";
    }
    #endregion
}
 

Комментарии:

1. См. Также с выражением (ссылка на C #) .

Ответ №2:

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

Вы могли бы определить IMyVector интерфейс с получением только свойств, MyVector реализовать его, а затем использовать интерфейс для вашего общедоступного Position свойства.

 public interface IMyVector
{
   float x {get;}
   ...
}
 
 public class MyVector : IMyVector
{
...
}
 
 public class Furniture : IMovable
{
   public string Name { get; private set; }
   public IMyVector Position { get; private set; }
   ...
 

Ответ №3:

Другая возможность проектирования — объявлять IMyReadOnlyVector интерфейс и предоставлять его всякий раз, когда мы не хотим разрешать векторы изменений:

 public interface IMyReadOnlyVector {
  float x { get; }
  float y { get; }
  float z { get; }
}

public interface IMyVector : IMyReadOnlyVector {
  float x { get; set; }
  float y { get; set; }
  float z { get; set; }
}
 

Затем вы реализуете MyVector :

 public class MyVector : IMyVector {
  public MyVector(float x, float y, float z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  public float x { get; set; }
  public float y { get; set; }
  public float z { get; set; }
}
 

Теперь время для хитрости: IMovable использует IMyReadOnlyVector интерфейс: мы позволяем пользователю видеть Position , но не разрешаем его изменять.

 public interface IMovable {
  string Name { get; }
  // User can see position, but not allowed to change it
  IMyReadOnlyVector Position { get; }
}

public class Furniture : IMovable {
  // Private usage only: we don't want user explicitly change position
  private MyVector m_Position;

  public string Name { get; private set; }

  // Public usage: user can't change vector's coordinates here
  public IMyReadOnlyVector Position => m_Position;

  public Furniture(string name, float x, float y, float z) {
    this.Name = name;
    this.m_Position = new MyVector(x, y, z);
  }

  // But we can change Position within the class 
  public void ShiftMe(int dx, int dy, int dz) {
    m_Position.x  = dx;
    m_Position.y  = dy;
    m_Position.z  = dz;
  }
}