/// <summary> /// Creates a new ValueMember instance /// </summary> public ValueMember( MemberMainSettingsValue memberSettings, ValueSerializationSettings valueSerializationSettings, MemberInfo member, Type parentType, RuntimeTypeModel model, bool isAccessHandledOutside = false, bool canHaveDefaultValue = true) { if (member == null) { throw new ArgumentNullException(nameof(member)); } if (parentType == null) { throw new ArgumentNullException(nameof(parentType)); } if (model == null) { throw new ArgumentNullException(nameof(model)); } MemberType = valueSerializationSettings.GetSettingsCopy(0).Basic.EffectiveType ?? Helpers.GetMemberType(member); Member = member; ParentType = parentType; _model = model; _canHaveDefaultValue = canHaveDefaultValue; _main = memberSettings; _isAccessHandledOutside = isAccessHandledOutside; _vsByClient = valueSerializationSettings.Clone(); if (!canHaveDefaultValue && _vsByClient.DefaultValue != null) { throw new ArgumentException("Default value was already set but the member " + Name + " can't have a default value", nameof(valueSerializationSettings)); } }
/// <summary> /// These settings are only applicable for collection types when they are serialized not as a member (root or LateReference) /// </summary> public MemberLevelSettingsValue GetNestedSettingsCopyWhenRoot(int level) { if (level == 0) { throw new ArgumentOutOfRangeException(nameof(level), "Zero level settings should be obtained through DefaultX properties on MetaType"); } return(_rootNestedVs.GetSettingsCopy(level).Basic); }
IProtoSerializerWithWireType BuildSerializer() { if (_serializer != null) { return(_serializer); } int opaqueToken = 0; try { // this lock is for whole model, not just this value member // so it should be taken anyway, right? _model.TakeLock(ref opaqueToken); // check nobody is still adding this type if (_serializer != null) { return(_serializer); // double check } FinalizingSettings?.Invoke(this, new FinalizingSettingsArgs(this)); if (FieldNumber < 1 && !Helpers.IsEnum(ParentType) && !_isAccessHandledOutside) { throw new ProtoException("FieldNumber < 1 for member " + Member); } if (Helpers.IsNullOrEmpty(_main.Name)) { _main.Name = Member.Name; } _vsFinal = _vsByClient.Clone(); var level0 = _vsFinal.GetSettingsCopy(0); if (level0.Basic.EffectiveType == null) { level0.Basic.EffectiveType = MemberType; } if (!level0.IsNotAssignable) { level0.IsNotAssignable = !Helpers.CanWrite(_model, Member); } _vsFinal.SetSettings(level0, 0); if (_vsFinal.DefaultValue != null && _getSpecified != null) { throw new ProtoException("Can't use default value \"" + _vsFinal.DefaultValue + "\" on member with *Specified or ShouldSerialize check " + Member); } if (_vsFinal.DefaultValue != null && IsRequired) { throw new ProtoException("Can't use default value \"" + _vsFinal.DefaultValue + "\" on Required member " + Member); } if (_getSpecified == null && !IsRequired && _vsFinal.DefaultValue == null) { if (_model.UseImplicitZeroDefaults) { switch (Helpers.GetTypeCode(MemberType)) { case ProtoTypeCode.Boolean: _vsFinal.DefaultValue = false; break; case ProtoTypeCode.Decimal: _vsFinal.DefaultValue = (decimal)0; break; case ProtoTypeCode.Single: _vsFinal.DefaultValue = (float)0; break; case ProtoTypeCode.Double: _vsFinal.DefaultValue = (double)0; break; case ProtoTypeCode.Byte: _vsFinal.DefaultValue = (byte)0; break; case ProtoTypeCode.Char: _vsFinal.DefaultValue = (char)0; break; case ProtoTypeCode.Int16: _vsFinal.DefaultValue = (short)0; break; case ProtoTypeCode.Int32: _vsFinal.DefaultValue = (int)0; break; case ProtoTypeCode.Int64: _vsFinal.DefaultValue = (long)0; break; case ProtoTypeCode.SByte: _vsFinal.DefaultValue = (sbyte)0; break; case ProtoTypeCode.UInt16: _vsFinal.DefaultValue = (ushort)0; break; case ProtoTypeCode.UInt32: _vsFinal.DefaultValue = (uint)0; break; case ProtoTypeCode.UInt64: _vsFinal.DefaultValue = (ulong)0; break; case ProtoTypeCode.TimeSpan: _vsFinal.DefaultValue = TimeSpan.Zero; break; case ProtoTypeCode.Guid: _vsFinal.DefaultValue = Guid.Empty; break; } } } #if !DEBUG try #endif { WireType wt; var ser = _model.ValueSerializerBuilder.BuildValueFinalSerializer( _vsFinal, true, out wt); if (!_isAccessHandledOutside) { PropertyInfo prop = Member as PropertyInfo; if (prop != null) { ser = new PropertyDecorator(_model, ParentType, (PropertyInfo)Member, ser); } else { FieldInfo fld = Member as FieldInfo; if (fld != null) { ser = new FieldDecorator(ParentType, (FieldInfo)Member, ser); } else { throw new InvalidOperationException(); } } if (_getSpecified != null || _setSpecified != null) { ser = new MemberSpecifiedDecorator(_getSpecified, _setSpecified, ser); } } _serializer = ser; return(ser); } #if !DEBUG catch (ProtoException ex) { throw new ProtoException(GetRethrowExceptionText(ex), ex); } catch (InvalidOperationException ex) { throw new InvalidOperationException(GetRethrowExceptionText(ex), ex); } catch (NotSupportedException ex) { throw new InvalidOperationException(GetRethrowExceptionText(ex), ex); } catch (NotImplementedException ex) { throw new NotImplementedException(GetRethrowExceptionText(ex), ex); } catch (ArgumentNullException ex) { throw new ArgumentNullException(GetRethrowExceptionText(ex), ex); } catch (ArgumentOutOfRangeException ex) { throw new ArgumentOutOfRangeException(GetRethrowExceptionText(ex), ex); } catch (ArgumentException ex) { #if SILVERLIGHT || PORTABLE throw new ArgumentException(GetRethrowExceptionText(ex), ex); #else throw new ArgumentException(GetRethrowExceptionText(ex), ex.ParamName, ex); #endif } catch (System.MissingMemberException ex) { throw new System.MissingMemberException(GetRethrowExceptionText(ex), ex); } catch (MemberAccessException ex) { throw new MemberAccessException(GetRethrowExceptionText(ex), ex); } catch (Exception ex) { throw new ProtoException(GetRethrowExceptionText(ex), ex); } #endif } finally { _model.ReleaseLock(opaqueToken); } }
ValueSerializationSettings.LevelValue CompleteLevel(ValueSerializationSettings vs, int levelNr, out object defaultValue) { var lv = vs.GetSettingsCopy(levelNr); var level = lv.Basic; // do not use lv.Basic, it's overwritten at the end of this method var originalLevel = level; if (levelNr == 0) { //#if WINRT if (vs.DefaultValue != null && _model.MapType(vs.DefaultValue.GetType()) != level.EffectiveType) //#else // if (defaultValue != null && !memberType.IsInstanceOfType(defaultValue)) //#endif { vs.DefaultValue = ParseDefaultValue(level.EffectiveType, vs.DefaultValue); } defaultValue = vs.DefaultValue; } else { defaultValue = null; } int idx = _model.FindOrAddAuto(level.EffectiveType, false, true, false); MetaType effectiveMetaType = null; if (idx >= 0) { effectiveMetaType = _model[idx]; var typeSettings = effectiveMetaType.GetFinalSettingsCopy(); level = MemberLevelSettingsValue.Merge(typeSettings.Member, level); } MemberDefaultsMode defaultsMode = level.DefaultsMode.GetValueOrDefault(); if (level.Format == ValueFormat.NotSpecified && defaultsMode == MemberDefaultsMode.Legacy) { level.Format = GetDefaultLegacyFormat(level.EffectiveType, _model); } if (level.Format == ValueFormat.LateReference) { if (vs.MaxSpecifiedNestedLevel > levelNr) { throw new ProtoException("LateReference member levels can't have nested levels"); } var defaultSettings = effectiveMetaType?.GetFinalSettingsCopy().Member ?? new MemberLevelSettingsValue().GetInitializedToValueOrDefault(); if (level.ContentBinaryFormatHint.GetValueOrDefault() != defaultSettings.ContentBinaryFormatHint.GetValueOrDefault() || level.WriteAsDynamicType.GetValueOrDefault() != defaultSettings.WriteAsDynamicType.GetValueOrDefault() || level.Collection.Append.GetValueOrDefault() != defaultSettings.Collection.Append.GetValueOrDefault() || level.Collection.PackedWireTypeForRead != defaultSettings.Collection.PackedWireTypeForRead) { throw new ProtoException("LateReference member levels can't override default member settings specified on MetaType"); } } Type newCollectionConcreteType = null; Type newItemType = null; if (!effectiveMetaType?.GetFinalSettingsCopy().IgnoreListHandling ?? true) { MetaType.ResolveListTypes(_model, level.EffectiveType, ref newItemType, ref newCollectionConcreteType); } // tuples depend on collection if (defaultsMode == MemberDefaultsMode.LegacyTuple && level.Format == ValueFormat.NotSpecified && newItemType != null) { level.Format = ValueFormat.Compact; } EnsureCorrectFormatSpecified(_model, ref level.Format, level.EffectiveType, ref level.WriteAsDynamicType, true); #region Collections { if (effectiveMetaType?.GetFinalSettingsCopy().IgnoreListHandling ?? false) { ResetCollectionSettings(ref level); } else { // defaults for ItemType and others were already merged from type settings if (level.Collection.ItemType != null) { if (level.Format == ValueFormat.LateReference) { Type defaultItemType = (effectiveMetaType?.GetFinalSettingsCopy().Member.Collection.ItemType ?? newItemType); if (level.Collection.ItemType != defaultItemType) { throw new ProtoException("LateReference member settings level should have default collection item type (" + defaultItemType + ")"); } } } else { level.Collection.ItemType = newItemType; } if (level.Collection.ItemType == null) { ResetCollectionSettings(ref level); } else { // should not override with default because: what if specified something like List<string> for IList? if (level.Collection.ConcreteType != null) { if (!Helpers.IsAssignableFrom(level.EffectiveType, level.Collection.ConcreteType)) { throw new ProtoException( "Specified CollectionConcreteType " + level.Collection.ConcreteType.Name + " is not assignable to " + level.EffectiveType); } if (level.Format == ValueFormat.LateReference) { Type defaultConcreteType = (effectiveMetaType?.GetFinalSettingsCopy().Member.Collection.ConcreteType ?? newCollectionConcreteType); if (level.Collection.ConcreteType != defaultConcreteType) { throw new ProtoException("LateReference member settings level should have default collection concrete type (" + defaultConcreteType + ")"); } } } else { level.Collection.ConcreteType = newCollectionConcreteType; } if (!level.Collection.Append.GetValueOrDefault() && lv.IsNotAssignable) { if (level.Collection.Append == null) { level.Collection.Append = true; } else { throw new ProtoException("The property is not writable but AppendCollection was set to false"); } } if (!CanPack(level.Collection.ItemType, level.ContentBinaryFormatHint)) { if (level.Collection.PackedWireTypeForRead != null && level.Collection.PackedWireTypeForRead != WireType.None) { throw new ProtoException("PackedWireTypeForRead " + level.Collection.PackedWireTypeForRead + " specified but type can't be packed"); } level.Collection.PackedWireTypeForRead = WireType.None; } if (level.Collection.Format == CollectionFormat.NotSpecified) { level.Collection.Format = !_model.ProtoCompatibility.SuppressCollectionEnhancedFormat ? CollectionFormat.Enhanced : CollectionFormat.Protobuf; } else if (level.Collection.Format == CollectionFormat.Enhanced && _model.ProtoCompatibility.SuppressCollectionEnhancedFormat) { level.Collection.Format = CollectionFormat.Protobuf; } } } if (!level.EffectiveType.IsArray) { if (level.Collection.ArrayLengthReadLimit != null) { throw new ProtoException("ArrayLengthReadLimit specified for non-array"); } } else if (level.Collection.ArrayLengthReadLimit == null) { level.Collection.ArrayLengthReadLimit = TypeModel.DefaultArrayLengthReadLimit; } else if (level.Collection.ArrayLengthReadLimit <= 0) { throw new ProtoException("ArrayLengthReadLimit should be greater than zero or not specified"); } } #endregion lv.Basic = level.GetInitializedToValueOrDefault(); vs.SetSettings(lv, levelNr); originalLevel.GetHashCode(); return(lv); }
IProtoSerializerWithWireType BuildValueFinalSerializer(ValueSerializationSettings settings, bool isMemberOrNested, out WireType wireType, int levelNumber) { object defaultValue; var l = CompleteLevel(settings, levelNumber, out defaultValue).Basic; // to ensure that model can be copied and used again for (int i = 1; i <= 3; i++) { var l2 = CompleteLevel(settings, levelNumber, out defaultValue); Debug.Assert(l.Equals(l2.Basic)); } Debug.Assert(l.ContentBinaryFormatHint != null, "l.ContentBinaryFormatHint != null"); Debug.Assert(l.WriteAsDynamicType != null, "l.WriteAsDynamicType != null"); Debug.Assert(l.Collection.Append != null, "l.Collection.Append != null"); // postpone all checks for types when adding member till BuildSerializer, resolve everything only on buildserializer! till that have only local not inherited settings. // do not allow EnumPassthru and other settings to affect anything until buildling serializer wireType = 0; Type itemType = l.Collection.ItemType ?? l.EffectiveType; bool itemTypeCanBeNull = CanTypeBeNull(itemType); bool isPacked = CanBePackedCollection(l); IProtoSerializerWithWireType ser = null; if (l.Collection.IsCollection) { Type nestedItemType = null; Type nestedDefaultType = null; int idx = _model.FindOrAddAuto(itemType, false, true, false); MetaType type = idx < 0 ? null : _model[itemType]; if (!type?.GetFinalSettingsCopy().IgnoreListHandling ?? true) { MetaType.ResolveListTypes(_model, itemType, ref nestedItemType, ref nestedDefaultType); } bool itemIsNestedCollection = nestedItemType != null; // primitive types except System.Object may be handled as nested through recursion bool tryHandleAsRegistered = !isMemberOrNested || itemType == _model.MapType(typeof(object)); if (tryHandleAsRegistered) { var nestedLevel = settings.GetSettingsCopy(levelNumber + 1); nestedLevel = PrepareNestedLevelForBuild(nestedLevel, itemType); settings.SetSettings(nestedLevel, levelNumber + 1); // should use its level settings and merge from type, ... ser = BuildValueFinalSerializer(settings, true, out wireType, levelNumber + 1); //object dummy = null; //ser = TryGetCoreSerializer(l.ContentBinaryFormatHint.Value, nestedLevel.Basic.EffectiveType, out wireType, ref nestedLevel.Basic.Format, nestedLevel.Basic.WriteAsDynamicType.GetValueOrDefault(), l.Collection.Append.Value, isPacked, true, ref dummy); //if (ser != null) // ThrowIfHasMoreLevels(settings, levelNumber + 1, l, ", no more nested type detected"); } else if (!itemIsNestedCollection) { var nestedLevel = settings.GetSettingsCopy(levelNumber + 1); nestedLevel = PrepareNestedLevelForBuild(nestedLevel, itemType); nestedLevel.Basic.Collection.ItemType = null; // IgnoreListHandling or not a collection settings.SetSettings(nestedLevel, levelNumber + 1); ser = BuildValueFinalSerializer(settings, true, out wireType, levelNumber + 1); } if (ser == null && itemIsNestedCollection) { // if we already tried to lookup registered type no need to do it again MetaType metaType; if (_model.FindOrAddAuto(itemType, false, true, false, out metaType) >= 0) { nestedDefaultType = metaType.GetFinalSettingsCopy().ConstructType ?? nestedDefaultType ?? metaType.Type; } var nestedLevel = settings.GetSettingsCopy(levelNumber + 1); if (nestedLevel.Basic.Collection.ConcreteType == null) { nestedLevel.Basic.Collection.ConcreteType = nestedDefaultType; } if (nestedLevel.IsNotAssignable) { throw new ProtoException("Nested collection item should be assignable"); } nestedLevel.Basic.Collection.Append = false; // TODO throw if set to true: throw new ProtoException("AppendCollection is not supported for nested types: " + objectType.Name); if (nestedLevel.Basic.Collection.ItemType == null) { nestedLevel.Basic.Collection.ItemType = nestedItemType; } else if (!Helpers.IsAssignableFrom(nestedItemType, nestedLevel.Basic.Collection.ItemType)) { throw new ProtoException( "Nested collection item type " + nestedLevel.Basic.Collection.ItemType + " is not assignable to " + nestedItemType + " for declared collection type " + l.EffectiveType); } nestedLevel = PrepareNestedLevelForBuild(nestedLevel, itemType); settings.SetSettings(nestedLevel, levelNumber + 1); WireType wt; ser = BuildValueFinalSerializer( settings, true, out wt, levelNumber + 1); isPacked = false; } } else { // handled outside and not wrapped with collection if (!isMemberOrNested) { l.Format = ValueFormat.Compact; } isPacked = false; // it's not even a collection ser = TryGetCoreSerializer(l.ContentBinaryFormatHint.Value, itemType, out wireType, ref l.Format, l.WriteAsDynamicType.Value, l.Collection.Append.Value, isPacked, true, ref defaultValue); if (ser != null) { ThrowIfHasMoreLevels(settings, levelNumber, l, ", no more nested type detected"); } } if (ser == null) { throw new InvalidOperationException("No serializer defined for type: " + itemType.FullName); } if (itemTypeCanBeNull && ( (Helpers.GetNullableUnderlyingType(itemType) != null && Helpers.GetNullableUnderlyingType(ser.ExpectedType) == null) // TODO get rid of ugly casting, maybe use builder pattern || (!Helpers.IsValueType(itemType) && !(ser is NetObjectValueDecorator)) )) { // nested level may be not collection and already wrapped with nonull, may later add check whether handled as registered vs as nested if (!(ser is NoNullDecorator)) { // if not wrapped with net obj - wrap with write-null check ser = new NoNullDecorator(_model, ser, l.Collection.ItemType != null); // throw only for collection elements, otherwise don't write } } if (itemType != ser.ExpectedType && (!l.WriteAsDynamicType.Value || !Helpers.IsAssignableFrom(ser.ExpectedType, itemType))) { throw new ProtoException(string.Format("Wrong type in the tail; expected {0}, received {1}", ser.ExpectedType, itemType)); } // apply lists if appropriate if (l.Collection.IsCollection) { bool protoCompatibility = l.Collection.Format == CollectionFormat.Protobuf || l.Collection.Format == CollectionFormat.ProtobufNotPacked; WireType packedReadWt; if (!protoCompatibility) { packedReadWt = WireType.None; Debug.Assert(!isPacked); // should be determinated before passing to TryGetCoreSerializer isPacked = false; } else { packedReadWt = CanPack(l.Collection.ItemType, l.ContentBinaryFormatHint) ? l.Collection.PackedWireTypeForRead.GetValueOrDefault(wireType) : WireType.None; } if (l.EffectiveType.IsArray) { if (l.EffectiveType.GetArrayRank() == 1) { ser = new ArrayDecorator( _model, ser, isPacked, packedReadWt, l.EffectiveType, !l.Collection.Append.Value, l.Collection.ArrayLengthReadLimit.Value, protoCompatibility); } else { if (protoCompatibility) { throw new NotSupportedException("Multi-dimensional arrays are supported only in Enhanced collection format"); } ser = new MultiDimensionalArrayDecorator( _model, ser, l.EffectiveType, !l.Collection.Append.Value, l.Collection.ArrayLengthReadLimit.Value); } } else { ser = ListDecorator.Create( _model, l.EffectiveType, l.Collection.ConcreteType, ser, isPacked, packedReadWt, !l.Collection.Append.Value, protoCompatibility, true); } if (isMemberOrNested) { if (MetaType.IsNetObjectValueDecoratorNecessary(_model, l.Format)) { ser = new NetObjectValueDecorator( ser, Helpers.GetNullableUnderlyingType(l.EffectiveType) != null, l.Format == ValueFormat.Reference || l.Format == ValueFormat.LateReference, l.Format == ValueFormat.LateReference && CanTypeBeAsLateReferenceOnBuildStage(_model.GetKey(l.EffectiveType, false, true), _model), !_model.ProtoCompatibility.SuppressNullWireType, _model); } else if (!Helpers.IsValueType(l.EffectiveType) || Helpers.GetNullableUnderlyingType(l.EffectiveType) != null) { ser = new NoNullDecorator(_model, ser, false); } } if (l.EffectiveType != ser.ExpectedType && (!l.WriteAsDynamicType.Value || !Helpers.IsAssignableFrom(ser.ExpectedType, itemType))) { throw new ProtoException(string.Format("Wrong type in the tail; expected {0}, received {1}", ser.ExpectedType, itemType)); } } if (levelNumber == 0 && defaultValue != null) { ser = new DefaultValueDecorator(_model, defaultValue, ser); } return(ser); }
private ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue) { if (Type.IsArray) { throw new InvalidOperationException("Can't add fields to array type"); } MemberInfo mi = null; #if WINRT mi = Helpers.IsEnum(Type) ? Type.GetTypeInfo().GetDeclaredField(memberName) : Helpers.GetInstanceMember(Type.GetTypeInfo(), memberName); #else MemberInfo[] members = Type.GetMember(memberName, Helpers.IsEnum(Type) ? BindingFlags.Static | BindingFlags.Public : BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (members != null && members.Length == 1) { mi = members[0]; } #endif if (mi == null) { throw new ArgumentException("Unable to determine member: " + memberName, nameof(memberName)); } Type miType; #if WINRT || PORTABLE PropertyInfo pi = mi as PropertyInfo; if (pi == null) { FieldInfo fi = mi as FieldInfo; if (fi == null) { throw new NotSupportedException(mi.GetType().Name); } else { miType = fi.FieldType; } } else { miType = pi.PropertyType; } #else switch (mi.MemberType) { case MemberTypes.Field: miType = ((FieldInfo)mi).FieldType; break; case MemberTypes.Property: miType = ((PropertyInfo)mi).PropertyType; break; default: throw new NotSupportedException(mi.MemberType.ToString()); } #endif // we can't check IgnoreListHandling (because of recursion when adding type) but we don't need to // it will be checked in ValueSerializedBuilder.CompleteLevel stage ResolveListTypes(_model, miType, ref itemType, ref defaultType); var serializationSettings = new ValueSerializationSettings(); var memberSettings = new MemberMainSettingsValue { Tag = fieldNumber }; var level0 = serializationSettings.GetSettingsCopy(0).Basic; level0.Collection.ConcreteType = defaultType; level0.Collection.ItemType = itemType; serializationSettings.SetSettings(level0, 0); serializationSettings.DefaultValue = defaultValue; var def = serializationSettings.DefaultLevel.GetValueOrDefault(); def.Basic = level0.MakeDefaultNestedLevel(); serializationSettings.DefaultLevel = def; ValueMember newField = new ValueMember(memberSettings, serializationSettings, mi, Type, _model); Add(newField); return(newField); }