在C#中,是什么使字段与属性不同?何时应该使用字段而不是属性?


当前回答

当您希望您的私有变量(字段)可以被其他类中的类对象访问时,您需要为这些变量创建财产。

例如,如果我的变量名为“id”,而“name”是私有的但可能存在这样的情况,即类外部的读/写操作需要此变量。在这种情况下,属性可以帮助我根据为属性定义的get/set来读取/写入该变量。属性可以是readonly/writeonly/readwriteboth。

这是演示

class Employee
{
    // Private Fields for Employee
    private int id;
    private string name;

    //Property for id variable/field
    public int EmployeeId
    {
       get
       {
          return id;
       }
       set
       {
          id = value;
       }
    }

    //Property for name variable/field
    public string EmployeeName
    {
       get
       {
          return name;
       }
       set
       {
          name = value;
       }
   }
}

class MyMain
{
    public static void Main(string [] args)
    {
       Employee aEmployee = new Employee();
       aEmployee.EmployeeId = 101;
       aEmployee.EmployeeName = "Sundaran S";
    }
}

其他回答

差异-用途(何时和为什么)

字段是直接在类或结构中声明的变量。类或结构可以具有实例字段或静态字段,也可以同时具有两者。通常,应仅对具有专用或受保护的可访问性的变量使用字段。类向客户端代码公开的数据应该通过方法、财产和索引器提供。通过使用这些构造间接访问内部字段,可以防止输入值无效。

属性是提供读取、写入或计算私有字段值的灵活机制的成员。财产可以像公共数据成员一样使用,但它们实际上是称为访问器的特殊方法。这使得数据可以很容易地访问,并且仍然有助于提高方法的安全性和灵活性。财产使类能够公开获取和设置值的公共方式,同时隐藏实现或验证代码。get属性访问器用于返回属性值,set访问器用于分配新值。

我对一个字段的设计是,一个字段只需要由它的父级修改,也就是类修改。结果变量变为私有,然后为了能够赋予读取外部类/方法的权限,我只使用Get来遍历属性系统。然后,该字段由属性检索,并且是只读的!如果你想修改它,你必须通过方法(例如构造函数),我发现由于这种方法使你安全,我们可以更好地控制代码,因为我们“法兰”。我们可以很好地将所有的事情都公开,所以每个可能的情况,变量/方法/类等的概念。。。在我看来,这只是对代码开发和维护的一种帮助。例如,如果一个人使用公共字段恢复代码,那么他可以做任何事情,因此可以做与目标相关的“不合逻辑”的事情,即代码编写的逻辑。这是我的观点。

当我使用经典模型私有字段/公共只读财产时,对于10个私有字段,我应该编写10个公共财产!代码可以更快地变大。我发现了私有setter,现在我只对私有setter使用公共财产。setter在后台创建一个私有字段。

这就是为什么我以前的经典编程风格是:

public class MyClass
{
 private int _id;
 public int ID { get { return _id; } }
 public MyClass(int id)
 {
  _id = id;
 }
}

我的新编程风格:

public class MyClass
{
 public int ID { get; private set; }
 public MyClass(int id)
 {
  ID = id;
 }
}

我将举几个使用财产的示例,这些属性可能会使齿轮转动:

惰性初始化:如果您有一个对象的属性,该属性的加载成本很高,但在正常运行代码时无法访问,则可以通过该属性延迟加载。这样,它就只是坐在那里,但当另一个模块第一次尝试调用该属性时,它会检查基础字段是否为空-如果为空,它会继续加载它,调用模块不知道。这可以大大加快对象初始化。肮脏追踪:这是我从StackOverflow上的问题中了解到的。当我有很多对象的值可能在运行期间发生了变化时,我可以使用该属性来跟踪它们是否需要保存回数据库。如果对象的任何一个属性都没有改变,IsDirty标志都不会被触发,因此保存功能在决定需要返回数据库时会跳过它。

当您希望您的私有变量(字段)可以被其他类中的类对象访问时,您需要为这些变量创建财产。

例如,如果我的变量名为“id”,而“name”是私有的但可能存在这样的情况,即类外部的读/写操作需要此变量。在这种情况下,属性可以帮助我根据为属性定义的get/set来读取/写入该变量。属性可以是readonly/writeonly/readwriteboth。

这是演示

class Employee
{
    // Private Fields for Employee
    private int id;
    private string name;

    //Property for id variable/field
    public int EmployeeId
    {
       get
       {
          return id;
       }
       set
       {
          id = value;
       }
    }

    //Property for name variable/field
    public string EmployeeName
    {
       get
       {
          return name;
       }
       set
       {
          name = value;
       }
   }
}

class MyMain
{
    public static void Main(string [] args)
    {
       Employee aEmployee = new Employee();
       aEmployee.EmployeeId = 101;
       aEmployee.EmployeeName = "Sundaran S";
    }
}

虽然字段和财产看起来很相似,但它们是两个完全不同的语言元素。

字段是在类级别上存储数据的唯一机制。字段在概念上是类范围内的变量。如果要将一些数据存储到类(对象)的实例中,则需要使用字段。别无选择。虽然财产无法存储任何数据,但看起来它们可以这样做。请参阅下文。另一方面,财产从不存储数据。它们只是一对方法(get和set),在语法上可以以与字段类似的方式调用,在大多数情况下,它们访问(用于读取或写入)字段,这是一些混淆的根源。但是,由于属性方法是(有一些限制,如固定原型)常规C#方法,它们可以做常规方法所能做的任何事情。这意味着它们可以有1000行代码,它们可以抛出异常,调用其他方法,甚至可以是虚拟的、抽象的或重写的。财产之所以特别,是因为C#编译器将一些额外的元数据存储到程序集中,这些程序集可用于搜索特定的财产,而这些属性是广泛使用的功能。

Get和set属性方法具有以下原型。

PROPERTY_TYPE get();

void set(PROPERTY_TYPE value);

因此,这意味着可以通过定义一个字段和两个相应的方法来“模拟”财产。

class PropertyEmulation
{
    private string MSomeValue;

    public string GetSomeValue()
    {
        return(MSomeValue);
    }

    public void SetSomeValue(string value)
    {
        MSomeValue=value;
    }
}

这种属性模拟对于不支持财产的编程语言来说是很典型的,比如标准C++。在C#中,您应该始终首选财产作为访问字段的方式。

因为只有字段可以存储数据,这意味着类包含的字段越多,此类的内存对象就会消耗越多。另一方面,向类中添加新的财产不会使此类对象变大。这是一个例子。

class OneHundredFields
{
        public int Field1;
        public int Field2;
        ...
        public int Field100;
}

OneHundredFields Instance=new OneHundredFields() // Variable 'Instance' consumes 100*sizeof(int) bytes of memory.

class OneHundredProperties
{
    public int Property1
    {
        get
        {
            return(1000);
        }
        set
        {
            // Empty.
        }
    }

    public int Property2
    {
        get
        {
            return(1000);
        }
        set
        {
            // Empty.
        }
    }

    ...

    public int Property100
    {
        get
        {
            return(1000);
        }
        set
        {
            // Empty.
        }
    }
}

OneHundredProperties Instance=new OneHundredProperties() // !!!!! Variable 'Instance' consumes 0 bytes of memory. (In fact a some bytes are consumed becasue every object contais some auxiliarity data, but size doesn't depend on number of properties).

尽管属性方法可以做任何事情,但在大多数情况下,它们充当了访问对象字段的一种方式。如果你想让一个字段可以被其他类访问,你可以通过两种方式来实现。

将字段设置为公共字段-不可取。使用财产。

这是一个使用公共字段的类。

class Name
{
    public string FullName;
    public int YearOfBirth;
    public int Age;
}

Name name=new Name();

name.FullName="Tim Anderson";
name.YearOfBirth=1979;
name.Age=40;

虽然从设计角度来看,代码是完全有效的,但它有几个缺点。因为字段既可以读也可以写,所以不能阻止用户写入字段。您可以应用readonly关键字,但通过这种方式,您只能在构造函数中初始化readonly字段。此外,没有什么可以阻止您将无效值存储到字段中。

name.FullName=null;
name.YearOfBirth=2200;
name.Age=-140;

代码有效,所有赋值都将执行,尽管它们不合逻辑。Age为负值,YearOfBirth是遥远的未来,与Age不对应,FullName为空。对于字段,您无法阻止类名的用户犯这样的错误。

下面是一个具有财产的代码,用于修复这些问题。

class Name
{
    private string MFullName="";
    private int MYearOfBirth;

    public string FullName
    {
        get
        {
            return(MFullName);
        }
        set
        {
            if (value==null)
            {
                throw(new InvalidOperationException("Error !"));
            }

            MFullName=value;
        }
    }

    public int YearOfBirth
    {
        get
        {
            return(MYearOfBirth);
        }
        set
        {
            if (MYearOfBirth<1900 || MYearOfBirth>DateTime.Now.Year)
            {
                throw(new InvalidOperationException("Error !"));
            }

            MYearOfBirth=value;
        }
    }

    public int Age
    {
        get
        {
            return(DateTime.Now.Year-MYearOfBirth);
        }
    }

    public string FullNameInUppercase
    {
        get
        {
            return(MFullName.ToUpper());
        }
    }
}

类的更新版本具有以下优点。

检查FullName和YearOfBirth是否存在无效值。年龄不可写。它是根据出生年份和当前年份计算的。新属性FullNameInUppercase将FullName转换为UPPER CASE。这是一个有点做作的属性用法示例,其中财产通常用于以更适合用户的格式显示字段值,例如在DateTime格式的特定数字上使用当前区域设置。

除此之外,可以将财产定义为虚拟或重写,因为它们是常规的.NET方法。与常规方法相同的规则适用于此类属性方法。

C#还支持索引器,索引器是在属性方法中具有索引参数的财产。这是一个例子。

class MyList
{
    private string[]                 MBuffer;

    public MyList()
    {
        MBuffer=new string[100];
    }

    public string this[int Index]
    {
        get
        {
            return(MBuffer[Index]);
        }
        set
        {
            MBuffer[Index]=value;
        }
    }
}

MyList   List=new MyList();

List[10]="ABC";
Console.WriteLine(List[10]);

因为C#3.0允许您定义自动财产。这是一个例子。

class AutoProps
{
    public int Value1
    {
        get;
        set;
    }

    public int Value2
    {
        get;
        set;
    }
}

尽管类AutoProps只包含财产(或看起来像),但它可以存储2个值,并且该类的对象大小等于sizeof(Value1)+size of(Value2)=4+4=8个字节。

原因很简单。当您定义自动属性时,C#编译器会生成包含隐藏字段和属性的自动代码,属性方法访问此隐藏字段。这是编译器生成的代码。

下面是ILSpy从编译的程序集生成的代码。类包含生成的隐藏字段和财产。

internal class AutoProps
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <Value1>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <Value2>k__BackingField;

    public int Value1
    {
        [CompilerGenerated]
        get
        {
            return <Value1>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <Value1>k__BackingField = value;
        }
    }

    public int Value2
    {
        [CompilerGenerated]
        get
        {
            return <Value2>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <Value2>k__BackingField = value;
        }
    }
}

因此,正如您所看到的,编译器仍然使用字段来存储值,因为字段是将值存储到对象中的唯一方法。

正如你所见,虽然财产和字段有相似的用法语法,但它们是非常不同的概念。即使您使用自动财产或事件,隐藏字段也由编译器生成,真实数据存储在这里。

如果需要使外部世界(类的用户)可以访问字段值,请不要使用公共或受保护的字段。字段始终应标记为私有。财产允许您进行值检查、格式化、转换等,通常会使代码更安全、更可读、更可扩展,以便将来进行修改。