/// <summary> /// Basic string projection where each bit is expressed, regardless of any checks. /// </summary> /// <param name="this">This CK type kind.</param> /// <returns>A readable string.</returns> public static string ToStringFlags(this CKTypeKind @this) { string[] flags = new[] { "IsAutoService", "IsScopedService", "IsSingleton", "IsRealObject", "IsPoco", "IsPocoClass", "IsFrontService", "IsFrontProcessService", "IsMarshallable", "IsMultipleService", "IsExcludedType", "HasCombinationError" }; if (@this == CKTypeKind.None) { return("None"); } var f = flags.Where((s, i) => (i == 0 && (@this & CKTypeKind.IsAutoService) != 0) || (i == 1 && (@this & CKTypeKind.IsScoped) != 0) || (i == 2 && (@this & CKTypeKind.IsSingleton) != 0) || (i == 3 && (@this & (CKTypeKind.RealObject & ~CKTypeKind.IsSingleton)) != 0) || (i == 4 && (@this & CKTypeKind.IsPoco) != 0) || (i == 5 && (@this & CKTypeKind.IsPocoClass) != 0) || (i == 6 && (@this & CKTypeKind.IsFrontService) != 0) || (i == 7 && (@this & CKTypeKind.IsFrontProcessService) != 0) || (i == 8 && (@this & CKTypeKind.IsMarshallable) != 0) || (i == 9 && (@this & CKTypeKind.IsMultipleService) != 0) || (i == 10 && (@this & CKTypeKind.IsExcludedType) != 0) || (i == 11 && (@this & CKTypeKind.HasCombinationError) != 0)); return(String.Join("|", f)); }
CKTypeKind?SetLifetimeOrFrontType(IActivityMonitor m, Type t, CKTypeKind kind) { bool hasLifetime = (kind & CKTypeKind.LifetimeMask) != 0; bool hasFrontType = (kind & CKTypeKind.FrontTypeMask) != 0; bool isMultiple = (kind & CKTypeKind.IsMultipleService) != 0; bool isMarshallable = (kind & CKTypeKind.IsMarshallable) != 0; Debug.Assert((kind & (IsDefiner | IsSuperDefiner)) == 0, "kind MUST not be a SuperDefiner or a Definer."); Debug.Assert(hasLifetime || hasFrontType || isMultiple || isMarshallable, "At least, something must be set."); Debug.Assert((kind & MaskPublicInfo).GetCombinationError(t.IsClass) == null, (kind & MaskPublicInfo).GetCombinationError(t.IsClass)); // This registers the type (as long as the Type detection is concerned): there is no difference between Registering first // and then defining lifetime or the reverse. (This is not true for the full type registration: SetLifetimeOrFrontType must // not be called for an already registered type.) var exist = RawGet(m, t); if ((exist & (IsDefiner | IsSuperDefiner)) != 0) { Throw.Exception($"Type '{t}' is a Definer or a SuperDefiner. It cannot be defined as {ToStringFull( kind )}."); } var updated = exist | kind; string?error = (updated & MaskPublicInfo).GetCombinationError(t.IsClass); if (error != null) { m.Error($"Type '{t}' is already registered as a '{ToStringFull( exist )}'. It can not be defined as {ToStringFull( kind )}. Error: {error}"); return(null); } _cache[t] = updated; Debug.Assert((updated & (IsDefiner | IsSuperDefiner)) == 0); Debug.Assert(CKTypeKindExtension.GetCombinationError((updated & MaskPublicInfo), t.IsClass) == null); return(updated & MaskPublicInfo); }
/// <summary> /// Gets the <see cref="AutoServiceKind"/> (masks the internal bits). /// Note that a check of the "IsFrontService => IsFrontProcessService and IsScoped" rule /// is made: an <see cref="ArgumentException"/> may be thrown. /// </summary> /// <param name="this">This type kind.</param> /// <returns>The Auto service kind.</returns> public static AutoServiceKind ToAutoServiceKind(this CKTypeKind @this) { if ((@this & CKTypeKind.IsFrontService) != 0 && (@this & (CKTypeKind.IsFrontProcessService | CKTypeKind.IsScoped)) != (CKTypeKind.IsFrontProcessService | CKTypeKind.IsScoped)) { Throw.ArgumentException($"Invalid CKTypeKind: IsFrontService must imply IsFrontProcessService and IsScoped."); } return((AutoServiceKind)((int)@this & 63)); }
internal MultipleImpl(Type tEnum, CKTypeKind enumerabledKind, Type tI, CKTypeKind iKind, CKTypeInfo first) { EnumerableType = tEnum; _enumerabledKind = enumerabledKind; EnumeratedType = tI; _itemKind = iKind; _rawImpls = new List <CKTypeInfo>(); _rawImpls.Add(first); // These properties are null until ComputeFinalTypeKind is called. // (Offensive) I prefer assuming this nullity here rather than setting empty arrays. MarshallableTypes = null !; MarshallableInProcessTypes = null !; }
bool DoRegisterClass(Type t, out RealObjectClassInfo?objectInfo, out AutoServiceClassInfo?serviceInfo) { Debug.Assert(t != null && t != typeof(object) && t.IsClass); // Skips already processed types. // The object collectors contains null RealObjectClassInfo and AutoServiceClassInfo value for // already processed types that are skipped or on error. serviceInfo = null; if (_objectCollector.TryGetValue(t, out objectInfo) || _serviceCollector.TryGetValue(t, out serviceInfo)) { return(false); } // Registers parent types whatever they are. RealObjectClassInfo? acParent = null; AutoServiceClassInfo?sParent = null; if (t.BaseType != typeof(object)) { Debug.Assert(t.BaseType != null, "Since t is not 'object'."); DoRegisterClass(t.BaseType, out acParent, out sParent); } CKTypeKind lt = KindDetector.GetRawKind(_monitor, t); if ((lt & CKTypeKind.HasCombinationError) == 0) { bool isExcluded = (lt & CKTypeKind.IsExcludedType) != 0; if (acParent != null || (lt & CKTypeKind.RealObject) == CKTypeKind.RealObject) { objectInfo = RegisterObjectClassInfo(t, isExcluded, acParent); Debug.Assert(objectInfo != null); } if (sParent != null || (lt & CKTypeKind.IsAutoService) != 0) { serviceInfo = RegisterServiceClassInfo(t, isExcluded, sParent, objectInfo); Debug.Assert(serviceInfo != null); } } // Marks the type as a registered one and gives it a chance to carry // Attributes... if (objectInfo == null && serviceInfo == null) { _objectCollector.Add(t, null); if ((lt & CKTypeKind.IsExcludedType) == 0) { RegisterRegularType(t); } } return(true); }
static string ToStringFull(CKTypeKind t) { var c = (t & MaskPublicInfo).ToStringFlags(); if ((t & IsDefiner) != 0) { c += " [IsDefiner]"; } if ((t & IsSuperDefiner) != 0) { c += " [IsSuperDefiner]"; } if ((t & IsReasonMarker) != 0) { c += " [IsMarkerInterface]"; } if ((t & IsLifetimeReasonExternal) != 0) { c += " [Lifetime:External]"; } if ((t & IsSingletonReasonReference) != 0) { c += " [Lifetime:ReferencedBySingleton]"; } if ((t & IsSingletonReasonFinal) != 0) { c += " [Lifetime:OpimizedAsSingleton]"; } if ((t & IsScopedReasonReference) != 0) { c += " [Lifetime:UsesScoped]"; } if ((t & IsMarshallableReasonMarshaller) != 0) { c += " [FrontType:MarshallableSinceMarshallerExists]"; } if ((t & IsFrontTypeReasonExternal) != 0) { c += " [FrontType:External]"; } if ((t & IsMultipleReasonExternal) != 0) { c += " [Multiple:External]"; } return(c); }
internal void AddMultipleMapping(Type t, CKTypeKind k, CKTypeCollector collector) { Debug.Assert(!IsSpecialized, "We are on the leaf."); Debug.Assert(t != Type, $"Multiple mapping {ToString()} must not be mapped to itself."); Debug.Assert(t.IsAssignableFrom(Type), $"Multiple mapping '{t}' must be assignable from {ToString()}!"); Debug.Assert(_multipleMappings == null || !_multipleMappings.Contains(t), $"Multiple mapping '{t}' already registered in {ToString()}."); Debug.Assert(_uniqueMappings == null || !_uniqueMappings.Contains(t), $"Multiple mapping '{t}' already registered in UNIQUE mappings of {ToString()}."); Debug.Assert((k & CKTypeKind.IsMultipleService) != 0); if (_multipleMappings == null) { _multipleMappings = new List <Type>(); } _multipleMappings.Add(t); if ((k & (CKTypeKind.IsFrontService | CKTypeKind.IsMarshallable)) != (CKTypeKind.IsFrontService | CKTypeKind.IsMarshallable)) { collector.RegisterMultipleInterfaces(t, k, this); } }
internal AutoServiceInterfaceInfo(TypeAttributesCache type, CKTypeKind lt, IEnumerable <AutoServiceInterfaceInfo> baseInterfaces) { Debug.Assert(lt == CKTypeKind.IsAutoService || lt == (CKTypeKind.IsAutoService | CKTypeKind.IsSingleton) || lt == (CKTypeKind.IsAutoService | CKTypeKind.IsScoped)); Attributes = type; InitialTypeKind = lt; AutoServiceInterfaceInfo[] bases = Array.Empty <AutoServiceInterfaceInfo>(); int depth = 0; foreach (var iT in baseInterfaces) { depth = Math.Max(depth, iT.SpecializationDepth + 1); Array.Resize(ref bases, bases.Length + 1); bases[bases.Length - 1] = iT; iT.IsSpecialized = true; } SpecializationDepth = depth; Interfaces = bases; }
/// <summary> /// Sets <see cref="AutoServiceKind"/> combination (that must not be <see cref="AutoServiceKind.None"/>) for a type. /// Can be called multiple times as long as no contradictory registration already exists (for instance, /// a <see cref="IRealObject"/> cannot be a Front service). /// Note that <see cref="AutoServiceKind.IsFrontService"/> is automatically expanded with <see cref="AutoServiceKind.IsScoped"/> /// and <see cref="AutoServiceKind.IsFrontProcessService"/>. /// </summary> /// <param name="m">The monitor.</param> /// <param name="t">The type to register.</param> /// <param name="kind">The kind of service. Must not be <see cref="AutoServiceKind.None"/>.</param> /// <returns>The type kind on success, null on error (errors - included combination ones - are logged).</returns> public CKTypeKind?SetAutoServiceKind(IActivityMonitor m, Type t, AutoServiceKind kind) { Throw.CheckArgument(kind != AutoServiceKind.None); bool hasFrontType = (kind & (AutoServiceKind.IsFrontProcessService | AutoServiceKind.IsFrontService)) != 0; bool hasLifetime = (kind & (AutoServiceKind.IsScoped | AutoServiceKind.IsSingleton)) != 0; bool hasMultiple = (kind & AutoServiceKind.IsMultipleService) != 0; CKTypeKind k = (CKTypeKind)kind; if (hasFrontType) { if ((kind & AutoServiceKind.IsFrontService) != 0) { k |= CKTypeKind.IsScoped; hasLifetime = true; } k |= CKTypeKind.IsFrontProcessService; } string?error = k.GetCombinationError(t.IsClass); if (error != null) { m.Error($"Invalid Auto Service kind registration '{k.ToStringFlags()}' for type '{t}'."); return(null); } if (hasLifetime) { k |= IsLifetimeReasonExternal; } if (hasMultiple) { k |= IsMultipleReasonExternal; } if (hasFrontType) { k |= IsFrontTypeReasonExternal; } return(SetLifetimeOrFrontType(m, t, k)); }
internal void RegisterMultipleInterfaces(Type tI, CKTypeKind enumeratedKind, CKTypeInfo final) { if (!_multipleMappings.TryGetValue(tI, out var multiple)) { Debug.Assert(enumeratedKind.GetCombinationError(false) == null); // A IEnumerable of IScoped is scoped, a IEnumerable of ISingleton is singleton, a IEnumerable of IFrontProcessService is // itself a front process service. Even a IEnumerable of an interface that has been marked [IsMarshallable] is de facto marshallable. // We rely on this here. Type tEnumerable = typeof(IEnumerable <>).MakeGenericType(tI); if ((enumeratedKind & (CKTypeKind.IsFrontService | CKTypeKind.IsMarshallable)) == (CKTypeKind.IsFrontService | CKTypeKind.IsMarshallable)) { // Only if the T interface is a IFrontService (and hence a Scoped) and is marked with IsMarshallable attribute // can we avoid the implementations analyzis. Even if a IFrontService interface marked with IsMarshallable attribute should be rare, // we can benefit here from this minor (but logical) optimization. _multipleMappings.Add(tI, new MultipleImpl(tEnumerable, tI)); } else { // The IEnumerable itself may have been explicitly registered via SetAutoServiceKind. // We check its compatibility with its enumerated interface (there may be incoherences) later in the DoComputeFinalTypeKind. // Here we just check the "worst case": CKTypeKind enumKind = KindDetector.GetValidKind(_monitor, tEnumerable); Debug.Assert(enumKind.GetCombinationError(false) == null); if ((enumKind & (CKTypeKind.IsFrontService | CKTypeKind.IsMarshallable)) == (CKTypeKind.IsFrontService | CKTypeKind.IsMarshallable)) { _multipleMappings.Add(tI, new MultipleImpl(tEnumerable, tI)); } else { _multipleMappings.Add(tI, new MultipleImpl(tEnumerable, enumKind, tI, enumeratedKind, final)); } } } else { multiple.AddImpl(final); } }
/// <summary> /// Gets the conflicting duplicate status message or null if this CK type kind is valid. /// </summary> /// <param name="this">This CK type kind.</param> /// <param name="isClass">True for Class type (not for interface).</param> /// <returns>An error message or null.</returns> public static string?GetCombinationError(this CKTypeKind @this, bool isClass) { Throw.CheckArgument(@this >= 0 && @this <= CKTypeKindDetector.MaskPublicInfo); // Pure predicates: checks are made against them. bool isAuto = (@this & CKTypeKind.IsAutoService) != 0; bool isScoped = (@this & CKTypeKind.IsScoped) != 0; bool isSingleton = (@this & CKTypeKind.IsSingleton) != 0; bool isRealObject = (@this & (CKTypeKind.RealObject & ~CKTypeKind.IsSingleton)) != 0; bool isPoco = (@this & CKTypeKind.IsPoco) != 0; bool isPocoClass = (@this & CKTypeKind.IsPocoClass) != 0; bool isFrontEndPoint = (@this & CKTypeKind.IsFrontService) != 0; bool isFrontProcess = (@this & CKTypeKind.IsFrontProcessService) != 0; bool isMarshallable = (@this & CKTypeKind.IsMarshallable) != 0; bool isMultiple = (@this & CKTypeKind.IsMultipleService) != 0; if (isFrontEndPoint && !isFrontProcess) { Throw.ArgumentException("CKTypeKind value error: missing IsFrontProcessService flag for IsFrontService: " + @this.ToStringFlags()); } if (isRealObject && !isSingleton) { Throw.Exception("CKTypeKind value error: missing IsSingleton flag to RealObject mask: " + @this.ToStringFlags()); } string?conflict = null; if (isPoco) { if (@this != CKTypeKind.IsPoco) { conflict = "Poco cannot be combined with any other aspect"; } if (isClass) { if (conflict != null) { conflict += ", "; } conflict += "A class cannot be a IPoco"; } } else if (isPocoClass) { if (@this != CKTypeKind.IsPocoClass) { conflict = "[PocoClass] class cannot be combined with any other aspect"; } } else if (isRealObject) { Debug.Assert(isSingleton, "Checked above."); if (@this != CKTypeKind.RealObject) { // If IsMultiple, then this is an interface, not a class: a IRealObject interface cannot be IsMultiple. if (isScoped) { conflict = "RealObject cannot have a Scoped lifetime"; } else if (isMultiple) { conflict = "IRealObject interface cannot be marked as a Multiple service"; } else if (isAuto && !isClass) { conflict = "IRealObject interface cannot be an IAutoService"; } // Always handle Front service. if (isMarshallable) { if (conflict != null) { conflict += ", "; } conflict += "RealObject cannot be marked as Marshallable"; } // Always handle Front service. if (isFrontEndPoint | isFrontProcess) { if (conflict != null) { conflict += ", "; } conflict += "RealObject cannot be a front service"; } } } else if (isScoped && isSingleton) { if (isFrontProcess) { conflict = "An interface or an implementation cannot be both Scoped and Singleton (since this is marked as a FrontService, this is de facto Scoped)"; } else { conflict = "An interface or an implementation cannot be both Scoped and Singleton"; } } if (isClass) { if ((@this & CKTypeKind.IsMultipleService) != 0) { conflict = "A class cannot be marked as a Multiple service: only interfaces can be IsMultiple."; } } return(conflict == null ? null : $"Invalid CK type combination: {conflict} for {(isClass ? "class" : "interface")} '{@this.ToStringFlags()}'."); }
/// <summary> /// Gets whether this <see cref="CKTypeKind"/> is <see cref="CKTypeKind.None"/> or /// is invalid (see <see cref="GetCombinationError(CKTypeKind,bool)"/>). /// </summary> /// <param name="this">This CK type kind.</param> /// <param name="isClass">True for Class type (not for interface).</param> /// <returns>whether this is invalid.</returns> public static bool IsNoneOrInvalid(this CKTypeKind @this, bool isClass) { return(@this == CKTypeKind.None || GetCombinationError(@this, isClass) != null); }
/// <summary> /// Returns a string that correctly handles flags and results to <see cref="GetCombinationError(CKTypeKind,bool)"/> /// if this kind is invalid. /// </summary> /// <param name="this">This CK type kind.</param> /// <param name="isClass">True for Class type (not for interface).</param> /// <returns>A readable string.</returns> public static string ToStringClear(this CKTypeKind @this, bool isClass) { string?error = GetCombinationError(@this, isClass); return(error == null?ToStringFlags(@this) : error); }