Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F#, serializing discriminated unions with values lacking data

First off, I have to say that I know it is generally not a good idea to use F# specific stuff when integrating with other languages in .NET.

My problem is that I don't understand how to create a Service Reference to a service containing methods exposing discriminated unions.

I get the basics that goes a little something like this:

  type TelephonyProductActivationData =
  | MobileUseNextIcc 
  | Mobile of decimal
  | MobileBroadbandUseNextIcc
  | MobileBroadband of decimal
  | Fixed
  | Voip of int16 * int16
  static member KnownTypes() =
      typeof<TelephonyProductActivationData>.GetNestedTypes(BindingFlags.Public ||| BindingFlags.NonPublic) |> Array.filter FSharpType.IsUnion

If you use F# interactive to first create the type:

type TelephonyProductActivationData =
  | MobileUseNextIcc of unit
  | Mobile of decimal<Icc>
  | MobileBroadbandUseNextIcc of unit
  | MobileBroadband of decimal<Icc>
  | Fixed of unit
  | Voip of BoxNr * int16<BoxPort>;;

And the you execute the knowntypes code portion (slightly modified):

(typeof<TelephonyProductActivationData>.GetNestedTypes(System.Reflection.BindingFlags.Public ||| System.Reflection.BindingFlags.NonPublic) |> Array.filter Microsoft.FSharp.Reflection.FSharpType.IsUnion) |> Array.map (fun x -> x.FullName);;

you will see the following output:

val it : string [] =
  [|"FSI_0047+TelephonyProductActivationData+Mobile";
    "FSI_0047+TelephonyProductActivationData+MobileBroadband";
    "FSI_0047+TelephonyProductActivationData+Voip"|]

Notice that the values not having data associated with them are gone. It means that no types will be created when compiling this discriminated union. By executing this statement in F# interactive:

typeof<TelephonyProductActivationData>.GetProperties() |> Array.map (fun x -> (x.Name));;

we will see what they have become:

val it : string [] =
  [|"Tag"; "IsVoip"; "Fixed"; "IsFixed"; "IsMobileBroadband";
    "MobileBroadbandUseNextIcc"; "IsMobileBroadbandUseNextIcc"; "IsMobile";
    "MobileUseNextIcc"; "IsMobileUseNextIcc"|]

As you can see the values without data associated with them have become properties. Now I can show you the real problem. When creating a service reference to the service with this method all I get is this:

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="ActivationModel.TelephonyProductActivationData", Namespace="http://schemas.datacontract.org/2004/07/Svea.Inri.Data")]
[System.SerializableAttribute()]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(ConsoleApplication1.ServiceReference1.ActivationModelTelephonyProductActivationData.Mobile))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(ConsoleApplication1.ServiceReference1.ActivationModelTelephonyProductActivationData.MobileBroadband))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(ConsoleApplication1.ServiceReference1.ActivationModelTelephonyProductActivationData.Voip))]
public partial class ActivationModelTelephonyProductActivationData : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {

    [System.NonSerializedAttribute()]
    private System.Runtime.Serialization.ExtensionDataObject extensionDataField;

    private int _tagField;

    [global::System.ComponentModel.BrowsableAttribute(false)]
    public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
        get {
            return this.extensionDataField;
        }
        set {
            this.extensionDataField = value;
        }
    }

    [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
    public int _tag {
        get {
            return this._tagField;
        }
        set {
            if ((this._tagField.Equals(value) != true)) {
                this._tagField = value;
                this.RaisePropertyChanged("_tag");
            }
        }
    }

    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));
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute(Name="ActivationModel.TelephonyProductActivationData.Mobile", Namespace="http://schemas.datacontract.org/2004/07/Svea.Inri.Data")]
    [System.SerializableAttribute()]
    public partial class Mobile : ConsoleApplication1.ServiceReference1.ActivationModelTelephonyProductActivationData {

        private decimal itemField;

        [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
        public decimal item {
            get {
                return this.itemField;
            }
            set {
                if ((this.itemField.Equals(value) != true)) {
                    this.itemField = value;
                    this.RaisePropertyChanged("item");
                }
            }
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute(Name="ActivationModel.TelephonyProductActivationData.MobileBroadband", Namespace="http://schemas.datacontract.org/2004/07/Svea.Inri.Data")]
    [System.SerializableAttribute()]
    public partial class MobileBroadband : ConsoleApplication1.ServiceReference1.ActivationModelTelephonyProductActivationData {

        private decimal itemField;

        [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
        public decimal item {
            get {
                return this.itemField;
            }
            set {
                if ((this.itemField.Equals(value) != true)) {
                    this.itemField = value;
                    this.RaisePropertyChanged("item");
                }
            }
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute(Name="ActivationModel.TelephonyProductActivationData.Voip", Namespace="http://schemas.datacontract.org/2004/07/Svea.Inri.Data")]
    [System.SerializableAttribute()]
    public partial class Voip : ConsoleApplication1.ServiceReference1.ActivationModelTelephonyProductActivationData {

        private string item1Field;

        private short item2Field;

        [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
        public string item1 {
            get {
                return this.item1Field;
            }
            set {
                if ((object.ReferenceEquals(this.item1Field, value) != true)) {
                    this.item1Field = value;
                    this.RaisePropertyChanged("item1");
                }
            }
        }

        [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
        public short item2 {
            get {
                return this.item2Field;
            }
            set {
                if ((this.item2Field.Equals(value) != true)) {
                    this.item2Field = value;
                    this.RaisePropertyChanged("item2");
                }
            }
        }
    }
}

There is no subclasses to ActivationModelTelephonyProductActivationData (the ActivationModel part is the namespace) that represents values not having any data and there are no properties in the baseclass where you can set the values not having any data.

My question is finally, how are one supposed to do this. Do I have to add "of unit" to all my discriminated union values that don't have data.

like image 525
Kristian Avatar asked Dec 19 '11 15:12

Kristian


2 Answers

If you define the DU type like below, it will work.

[<KnownType("KnownTypes")>]
//[<DataContract>] // note: keep KnownTypes, but avoid DataContract 
//  so that DataContractSerializer uses .NET 'Serializable' instead
type TelephonyProductActivationData = 
  | MobileUseNextIcc
  | Mobile of decimal 
  | MobileBroadbandUseNextIcc
  | MobileBroadband of decimal 
  | Fixed
  | Voip of int16 * int16 
  static member KnownTypes() = 
      typeof<TelephonyProductActivationData>.GetNestedTypes(BindingFlags.Public |||
                                                            BindingFlags.NonPublic) 
      |> Array.filter FSharpType.IsUnion 
like image 184
Brian Avatar answered Nov 03 '22 19:11

Brian


You are essentially depending on an implementation detail (the compiled form of DUs) for this to work. Even changing each case to be non-nullary smells hacky to me. I think the ideal solution is to use classes. A DU roughly corresponds to an abstract base class for the DU type and a subclass for each case. You can create the type hierarchy yourself, achieve a similar effect, and get better results.

EDIT: The compiled form of DUs, while an implementation detail, is defined in the spec and therefore unlikely to change. However, laying out the types yourself makes it explicit and prevents you from having to work around nullary cases.

like image 20
Daniel Avatar answered Nov 03 '22 19:11

Daniel