問題陳述
有一個自定義向量類:
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);
}
}
}
正如預期的那樣,訪問 Name 的私有 getter 是不可能的。正如預期的那樣,訪問 Position 的私有 setter 也不起作用。但是,可以訪問 Position 欄位,因為它們是公開的。
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如果它的值不能改變,那么在其他地方就會變得無用。
uj5u.com熱心網友回復:
這里有幾點要說明:
根據設計,您選擇制作欄位
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; } }通常,您不會公開欄位,而是使用僅定義了 getter 的屬性。
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; } }此外,您可以使用自動屬性簡化事情
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; } }最后,它推薦用于值語意,其中 (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; }
}

So even with by design allowing the contents of MyVector to be mutable (change), the compiler is going to stop you. This is because with struct types you have local copies of the data everywhere and by writing Position.x = 10f you would have modified a local copy of Position that exists in the scope where this is called, and not modified the original data.
In the question MyVector is a class and so Position.x = 10f modifies the original data and as stated this is undesirable behavior, so follow the steps above to disallow this behavior.
To make MyVector work well with other classes I often add the following functionality to such deflations. I add support for .ToString() with formatting and I add support for .Equals() (and == for structures) in order to be to write code like this:
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 個小數的格式和相等性檢查。
這是允許您參考的完整代碼
我的矢量檔案
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)
&& Y.Equals(other.Y)
&& 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
}
uj5u.com熱心網友回復:
為物件使用私有 setter 的問題在于它只會阻止您完全替換物件。由于它不是一個不可變的物件,您仍然可以訪問它的屬性來更改它們,正如您所發現的。
您可以定義一個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; }
...
uj5u.com熱心網友回復:
另一種設計可能性是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;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/372182.html
標籤:C#
