Reputation: 13765
I encountered an (what I consider to be) issue with a service reference generation.
Original class (example)
[Serializable()]
public class Foo
{
private int _Bar;
public int Bar
{
get { return _Bar; }
set { _Bar = value; }
}
public Foo()
{
this._Bar = 42;
}
}
I found it strange that the constructor was using the private backing field rather than using the public setter, so I refactored to this:
[Serializable()]
public class Foo
{
public int Bar { get; set; }
public Foo()
{
this.Bar = 42;
}
}
these two seem equivalent enough I believe... however when I regenerated my service reference that contains a reference to Foo... I received a compile error.
No reference/extension method for _Bar exists in Foo
Note this is only what I can remember of the compile error since this is only a generalized example of what I encountered. There was existing code reliant on this service reference, which somehow referenced Foo._Bar
- even though it is private.
So... is this the expected behavior? My re-factored class even though looking equivalent to me... generated a reference class in a way I didn't expect.
I'm assuming because the private _Bar
was referenced directly in the constructor, it was somehow serialized with the class even though it was private?
I'm worried about this behavior, as I did similar refactoring in numerous places in our code base - am I not understanding something about how the serializing classes works?
Edit:
I'm noticing that the original Reference file created on the Foo
class looks like this:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Foo", Namespace="http://schemas.datacontract.org/2004/07/Foo")]
[System.SerializableAttribute()]
public partial class Foo: object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
[System.NonSerializedAttribute()]
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
private int _BarField;
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
public int _Bar {
get {
return this._BarField;
}
set {
if ((this._BarField.Equals(value) != true)) {
this._BarField = value;
this.RaisePropertyChanged("_Bar");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
I guess I expected Bar
to be the accessible property in the Reference file from the original class, not _Bar
- but that assumption was incorrect in this case. Is there something I'm missing here? Why would the reference file be generated with the private _Bar
as the property, rather than the public Bar
which is used as a getter and setter for the private backing field?
Upvotes: 0
Views: 609
Reputation: 116554
This behavior occurs because you have marked your class with the Serializable
attribute, but no data contract attributes. According to Types Supported by the Data Contract Serializer,
The following is a complete list of types that can be serialized:
- Types marked with the SerializableAttribute attribute. Many types included in the .NET Framework base class library fall into this category. The DataContractSerializer fully supports this serialization programming model that was used by .NET Framework remoting, the BinaryFormatter, and the SoapFormatter, including support for the ISerializable interface.
So, how does this "serialization programming model" work? From the docs:
When you apply the
SerializableAttribute
attribute to a type, all private and public fields are serialized by default.
Thus you have (unintentionally) instructed the data contract serializer to auto-generate a contract that serializes private and public fields of your class, not the properties. Then when you switch your property to be auto-implemented, you are changing the name of the its backing field from _Bar
to the name of the hidden backing field. In turn the contract inferred from the type contains a renamed member. You can see the change in the contract when you serialize to XML. Here is the original field being serialized in the original XML:
<Foo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question29495337.V1"> <_Bar>42</_Bar> </Foo>
And the backing field in the new XML:
<Foo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question29495337.V2"> <_x003C_Bar_x003E_k__BackingField>42</_x003C_Bar_x003E_k__BackingField> </Foo>
Then when you do add service reference in a client, Visual Studio auto-generates a data contract type with public properties named after the serializable data contract members. Since those members got named after the private fields, this promotes the private field names on the server to public property names in the client, making seemingly private aspects of your class public.
You have several ways to avoid this problem:
Extract the serializable type into a DLL and link it into both the client and server. In this case the auto-generated data contract won't matter.
Remove the [Serializable]
attribute. Doing so will cause DataContractSerializer
to infer a contract that serializes all public fields, and properties with public get and set methods.
If you cannot remove [Serializable]
, annotate the class with explicit data contract attributes. These will override the auto-generated Serializable
contract and stabilize the contract member names.
Upvotes: 1