TypeHelpers.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataEntity / System / Data / Metadata / TypeHelpers.cs / 1305376 / TypeHelpers.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright(c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner       [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

namespace System.Data.Common 
{
    using System;
    using System.Collections;
    using System.Collections.Generic; 
    using System.Globalization;
    using System.Data.Common.CommandTrees; 
    using System.Data.Metadata.Edm; 
    using System.Data;
    using System.Diagnostics; 

    /// 
    /// Represents a set of static Type helpers operating on TypeMetadata
    ///  
    internal static class TypeHelpers
    { 
        #region Assert Types 

        ///  
        /// Asserts types are in Model space
        /// 
        /// 
        [Conditional("DEBUG")] 
        internal static void AssertEdmType(TypeUsage typeUsage)
        { 
            EdmType type = typeUsage.EdmType; 
            if (TypeSemantics.IsCollectionType(typeUsage))
            { 
                AssertEdmType(TypeHelpers.GetElementTypeUsage(typeUsage));
            }
            else if (TypeSemantics.IsStructuralType(typeUsage) && !Helper.IsComplexType(typeUsage.EdmType) && !Helper.IsEntityType(typeUsage.EdmType))
            { 
                foreach (EdmMember m in TypeHelpers.GetDeclaredStructuralMembers(typeUsage))
                { 
                    AssertEdmType(m.TypeUsage); 
                }
            } 
            else if (TypeSemantics.IsPrimitiveType(typeUsage))
            {
                PrimitiveType pType = type as PrimitiveType;
                if (null != pType) 
                {
                    if (pType.DataSpace != DataSpace.CSpace) 
                        throw new NotSupportedException(String.Format(CultureInfo.InvariantCulture, "PrimitiveType must be CSpace '{0}'", typeUsage.ToString())); 
                }
            } 
        }

        /// 
        /// Asserts querycommandtrees are in model space type terms 
        /// 
        ///  
        [Conditional("DEBUG")] 
        internal static void AssertEdmType(DbCommandTree commandTree)
        { 
            DbQueryCommandTree queryCommandTree = commandTree as DbQueryCommandTree;
            if (null != queryCommandTree)
            {
                AssertEdmType(queryCommandTree.Query.ResultType); 
            }
        } 
        #endregion 

        // 
        // Type Semantics
        //
        #region Type Semantics
 
        /// 
        /// Determines whether a given typeUsage is valid as OrderBy sort key 
        ///  
        /// 
        ///  
        internal static bool IsValidSortOpKeyType(TypeUsage typeUsage)
        {
            if (TypeSemantics.IsRowType(typeUsage))
            { 
                RowType rowType = (RowType)typeUsage.EdmType;
                foreach (EdmProperty property in rowType.Properties) 
                { 
                    if (!IsValidSortOpKeyType(property.TypeUsage))
                    { 
                        return false;
                    }
                }
                return true; 
            }
            else 
            { 
                return TypeSemantics.IsOrderComparable(typeUsage);
            } 
        }

        /// 
        /// Determines whether a given typeusage is valid as GroupBy key 
        /// 
        ///  
        ///  
        internal static bool IsValidGroupKeyType(TypeUsage typeUsage)
        { 
            return IsSetComparableOpType(typeUsage);
        }

        ///  
        /// Determine wheter a given typeusage is valid for Distinct operator
        ///  
        ///  
        /// 
        internal static bool IsValidDistinctOpType(TypeUsage typeUsage) 
        {
            return IsSetComparableOpType(typeUsage);
        }
 
        /// 
        /// Determine wheter a given typeusage is valid for set comparison operator such as UNION, INTERSECT and EXCEPT 
        ///  
        /// 
        ///  
        internal static bool IsSetComparableOpType(TypeUsage typeUsage)
        {
            if (Helper.IsEntityType(typeUsage.EdmType)    ||
                Helper.IsPrimitiveType(typeUsage.EdmType) || 
                Helper.IsRefType(typeUsage.EdmType)        )
            { 
                return true; 
            }
            else if (TypeSemantics.IsRowType(typeUsage)) 
            {
                RowType rowType = (RowType)typeUsage.EdmType;
                foreach (EdmProperty property in rowType.Properties)
                { 
                    if (!IsSetComparableOpType(property.TypeUsage))
                    { 
                        return false; 
                    }
                } 
                return true;
            }
            return false;
        } 

        ///  
        /// Returns true if typeUsage type is valid for IS [NOT] NULL (expr) operator 
        /// 
        ///  
        /// 
        internal static bool IsValidIsNullOpType(TypeUsage typeUsage)
        {
            return TypeSemantics.IsReferenceType(typeUsage) || 
                   TypeSemantics.IsEntityType(typeUsage)    ||
                   TypeSemantics.IsPrimitiveType(typeUsage) || 
                   TypeSemantics.IsNullType(typeUsage); 
        }
 

        internal static bool IsValidInOpType(TypeUsage typeUsage)
        {
            return TypeSemantics.IsReferenceType(typeUsage) || 
                   TypeSemantics.IsPrimitiveType(typeUsage) ||
                   TypeSemantics.IsEntityType(typeUsage); 
        } 

        internal static TypeUsage GetCommonTypeUsage(TypeUsage typeUsage1, TypeUsage typeUsage2) 
        {
            return TypeSemantics.GetCommonType(typeUsage1, typeUsage2);
        }
 
        internal static TypeUsage GetCommonTypeUsage(IEnumerable types)
        { 
            TypeUsage commonType = null; 
            foreach (TypeUsage testType in types)
            { 
                if (null == testType)
                {
                    return null;
                } 

                if (null == commonType) 
                { 
                    commonType = testType;
                } 
                else
                {
                    commonType = TypeSemantics.GetCommonType(commonType, testType);
                    if (null == commonType) 
                    {
                        break; 
                    } 
                }
            } 

            return commonType;
        }
 
        #endregion
 
        // 
        // Type property extractors
        // 
        #region Type property extractors

        internal static bool TryGetClosestPromotableType(TypeUsage fromType, out TypeUsage promotableType)
        { 
            promotableType = null;
            if (Helper.IsPrimitiveType(fromType.EdmType)) 
            { 
                PrimitiveType fromPrimitiveType = (PrimitiveType)fromType.EdmType;
                IList promotableTypes = EdmProviderManifest.Instance.GetPromotionTypes(fromPrimitiveType); 
                int index = promotableTypes.IndexOf(fromPrimitiveType);
                if (-1 != index && index + 1 < promotableTypes.Count)
                {
                    promotableType = TypeUsage.Create(promotableTypes[index + 1]); 
                }
            } 
            return (null != promotableType); 
        }
 

        #endregion

        // 
        // Facet Helpers
        // 
        #region Facet Helpers 

        internal static bool TryGetBooleanFacetValue(TypeUsage type, string facetName, out bool boolValue) 
        {
            boolValue = false;
            Facet boolFacet;
            if (type.Facets.TryGetValue(facetName, false, out boolFacet) && boolFacet.Value != null) 
            {
                boolValue = (bool)boolFacet.Value; 
                return true; 
            }
 
            return false;
        }

        internal static bool TryGetByteFacetValue(TypeUsage type, string facetName, out byte byteValue) 
        {
            byteValue = 0; 
            Facet byteFacet; 
            if (type.Facets.TryGetValue(facetName, false, out byteFacet) && byteFacet.Value != null && !Helper.IsUnboundedFacetValue(byteFacet))
            { 
                byteValue = (byte)byteFacet.Value;
                return true;
            }
 
            return false;
        } 
 
        internal static bool TryGetIntFacetValue(TypeUsage type, string facetName, out int intValue)
        { 
            intValue = 0;
            Facet intFacet;
            if (type.Facets.TryGetValue(facetName, false, out intFacet) && intFacet.Value != null && !Helper.IsUnboundedFacetValue(intFacet))
            { 
                intValue = (int)intFacet.Value;
                return true; 
            } 

            return false; 
        }

        internal static bool TryGetIsFixedLength(TypeUsage type, out bool isFixedLength)
        { 
            if (!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String) &&
                !TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.Binary)) 
            { 
                isFixedLength = false;
                return false; 
            }

            // Binary and String MaxLength facets share the same name
            return TypeHelpers.TryGetBooleanFacetValue(type, DbProviderManifest.FixedLengthFacetName, out isFixedLength); 
        }
 
        internal static bool TryGetIsUnicode(TypeUsage type, out bool isUnicode) 
        {
            if (!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String)) 
            {
                isUnicode = false;
                return false;
            } 

            return TypeHelpers.TryGetBooleanFacetValue(type, DbProviderManifest.UnicodeFacetName, out isUnicode); 
        } 

        internal static bool IsFacetValueConstant(TypeUsage type, string facetName) 
        {
            // Binary and String FixedLength facets share the same name
            return Helper.GetFacet(((PrimitiveType)type.EdmType).FacetDescriptions, facetName).IsConstant;
        } 

        internal static bool TryGetMaxLength(TypeUsage type, out int maxLength) 
        { 
            if (!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.String) &&
                !TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.Binary)) 
            {
                maxLength = 0;
                return false;
            } 

            // Binary and String FixedLength facets share the same name 
            return TypeHelpers.TryGetIntFacetValue(type, DbProviderManifest.MaxLengthFacetName, out maxLength); 
        }
 
        internal static bool TryGetPrecision(TypeUsage type, out byte precision)
        {
            if (!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.Decimal))
            { 
                precision = 0;
                return false; 
            } 

            return TypeHelpers.TryGetByteFacetValue(type, DbProviderManifest.PrecisionFacetName, out precision); 
        }

        internal static bool TryGetScale(TypeUsage type, out byte scale)
        { 
            if (!TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.Decimal))
            { 
                scale = 0; 
                return false;
            } 

            return TypeHelpers.TryGetByteFacetValue(type, DbProviderManifest.ScaleFacetName, out scale);
        }
 
        internal static bool TryGetPrimitiveTypeKind(TypeUsage type, out PrimitiveTypeKind typeKind)
        { 
            if (type != null && type.EdmType != null && type.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType) 
            {
                typeKind = ((PrimitiveType)type.EdmType).PrimitiveTypeKind; 
                return true;
            }

            typeKind = default(PrimitiveTypeKind); 
            return false;
        } 
 
        #endregion
 
        //
        // Type Constructors
        //
        #region Type Constructors 
        internal static CollectionType CreateCollectionType(TypeUsage elementType)
        { 
            return new CollectionType(elementType); 
        }
 
        internal static TypeUsage CreateCollectionTypeUsage(TypeUsage elementType)
        {
            return CreateCollectionTypeUsage(elementType, false /* readOnly */ );
        } 

        internal static TypeUsage CreateCollectionTypeUsage(TypeUsage elementType, bool readOnly) 
        { 
            return TypeUsage.Create(new CollectionType(elementType));
        } 

        internal static RowType CreateRowType(IEnumerable> columns)
        {
            List rowElements = new List(); 
            foreach (KeyValuePair kvp in columns)
            { 
                rowElements.Add(new EdmProperty(kvp.Key, kvp.Value)); 
            }
            return new RowType(rowElements); 
        }

        internal static TypeUsage CreateRowTypeUsage(IEnumerable> columns, bool readOnly)
        { 
            return TypeUsage.Create(CreateRowType(columns));
        } 
 
        internal static RefType CreateReferenceType(EntityTypeBase entityType)
        { 
            return new RefType((EntityType)entityType);
        }

        internal static TypeUsage CreateReferenceTypeUsage(EntityType entityType) 
        {
            return TypeUsage.Create(CreateReferenceType(entityType)); 
        } 

        ///  
        /// Creates metadata for a new row type with column names and types based on the key members of the specified Entity type
        /// 
        /// The Entity type that provides the Key members on which the column names and types of the new row type will be based
        /// A new RowType info with column names and types corresponding to the Key members of the specified Entity type 
        internal static RowType CreateKeyRowType(EntityTypeBase entityType)
        { 
            IEnumerable entityKeys = entityType.KeyMembers; 
            if (null == entityKeys)
            { 
                throw EntityUtil.Argument(System.Data.Entity.Strings.Cqt_Metadata_EntityTypeNullKeyMembersInvalid, "entityType");
            }

            List> resultCols = new List>(); 
            //int idx = 0;
            foreach (EdmProperty keyProperty in entityKeys) 
            { 
                //this.CheckMember(keyProperty, "property", CommandTreeUtils.FormatIndex("entityType.KeyMembers", idx++));
                resultCols.Add(new KeyValuePair(keyProperty.Name, Helper.GetModelTypeUsage(keyProperty))); 
            }

            if (resultCols.Count < 1)
            { 
                throw EntityUtil.Argument(System.Data.Entity.Strings.Cqt_Metadata_EntityTypeEmptyKeyMembersInvalid, "entityType");
            } 
 
            return TypeHelpers.CreateRowType(resultCols);
        } 
        #endregion

        //
        // Type extractors 
        //
        #region Type Extractors 
 
        /// 
        /// Retrieves Properties and/or RelationshipEnds declared by the specified type or any base type. 
        /// 
        /// 
        /// 
        internal static IBaseList GetAllStructuralMembers(TypeUsage type) 
        {
            return GetAllStructuralMembers(type.EdmType); 
        } 

        internal static IBaseList GetAllStructuralMembers(EdmType edmType) 
        {
            System.Diagnostics.Debug.Assert(edmType != null);
            switch (edmType.BuiltInTypeKind)
            { 
                case BuiltInTypeKind.AssociationType:
                    return (IBaseList)((AssociationType)edmType).AssociationEndMembers; 
                case BuiltInTypeKind.ComplexType: 
                    return (IBaseList)((ComplexType)edmType).Properties;
                case BuiltInTypeKind.EntityType: 
                    return (IBaseList)((EntityType)edmType).Properties;
                case BuiltInTypeKind.RowType:
                    return (IBaseList)((RowType)edmType).Properties;
                default: 
                    return EmptyArrayEdmProperty;
            } 
        } 

        ///  
        /// Retrieves Properties and/or RelationshipEnds declared by (and ONLY by) the specified type.
        /// 
        /// 
        ///  
        internal static IEnumerable GetDeclaredStructuralMembers(TypeUsage type)
        { 
            return GetDeclaredStructuralMembers(type.EdmType); 
        }
 
        /// 
        /// Retrieves Properties and/or RelationshipEnds declared by (and ONLY by) the specified type.
        /// 
        ///  
        /// 
        internal static IEnumerable GetDeclaredStructuralMembers(EdmType edmType) 
        { 
            switch (edmType.BuiltInTypeKind)
            { 
                case BuiltInTypeKind.AssociationType:
                    return ((AssociationType)edmType).GetDeclaredOnlyMembers();
                case BuiltInTypeKind.ComplexType:
                    return ((ComplexType)edmType).GetDeclaredOnlyMembers(); 
                case BuiltInTypeKind.EntityType:
                    return ((EntityType)edmType).GetDeclaredOnlyMembers(); 
                case BuiltInTypeKind.RowType: 
                    return ((RowType)edmType).GetDeclaredOnlyMembers();
                default: 
                    return EmptyArrayEdmProperty;
            }
        }
 
        internal static readonly ReadOnlyMetadataCollection EmptyArrayEdmMember = new ReadOnlyMetadataCollection(new MetadataCollection().SetReadOnly());
        internal static readonly FilteredReadOnlyMetadataCollection EmptyArrayEdmProperty = new FilteredReadOnlyMetadataCollection(EmptyArrayEdmMember, null); 
 
        internal static ReadOnlyMetadataCollection GetProperties(TypeUsage typeUsage)
        { 
            return GetProperties(typeUsage.EdmType);
        }

        internal static ReadOnlyMetadataCollection GetProperties(EdmType edmType) 
        {
            switch (edmType.BuiltInTypeKind) 
            { 
                case BuiltInTypeKind.ComplexType:
                    return ((ComplexType)edmType).Properties; 
                case BuiltInTypeKind.EntityType:
                    return ((EntityType)edmType).Properties;
                case BuiltInTypeKind.RowType:
                    return ((RowType)edmType).Properties; 
                default:
                    return EmptyArrayEdmProperty; 
            } 
        }
 
        internal static TypeUsage GetElementTypeUsage(TypeUsage type)
        {
            if (TypeSemantics.IsCollectionType(type))
            { 
                return ((CollectionType)type.EdmType).TypeUsage;
            } 
            else if (TypeSemantics.IsReferenceType(type)) 
            {
                return TypeUsage.Create(((RefType)type.EdmType).ElementType); 
            }

            return null;
        } 

        // 
        // Element type 
        //
        internal static bool TryGetCollectionElementType(TypeUsage type, out TypeUsage elementType) 
        {
            CollectionType collectionType;
            if (TypeHelpers.TryGetEdmType(type, out collectionType))
            { 
                elementType = collectionType.TypeUsage;
                return (elementType != null); 
            } 

            elementType = null; 
            return false;
        }

        ///  
        /// If the type refered to by the TypeUsage is a RefType, extracts the EntityType and returns true,
        /// otherwise returns false. 
        ///  
        /// TypeUsage that may or may not refer to a RefType
        /// Non-null if the TypeUsage refers to a RefType, null otherwise 
        /// True if the TypeUsage refers to a RefType, false otherwise
        internal static bool TryGetRefEntityType(TypeUsage type, out EntityType referencedEntityType)
        {
            RefType refType; 
            if (TryGetEdmType(type, out refType) &&
                Helper.IsEntityType(refType.ElementType)) 
            { 
                referencedEntityType = (EntityType)refType.ElementType;
                return true; 
            }

            referencedEntityType = null;
            return false; 
        }
 
        internal static TEdmType GetEdmType(TypeUsage typeUsage) 
            where TEdmType : EdmType
        { 
            return (TEdmType)typeUsage.EdmType;
        }

        internal static bool TryGetEdmType(TypeUsage typeUsage, out TEdmType type) 
            where TEdmType : EdmType
        { 
            type = typeUsage.EdmType as TEdmType; 
            return (type != null);
        } 
        #endregion

        //
        // Misc 
        //
        #region Misc 
        internal static TypeUsage GetReadOnlyType(TypeUsage type) 
        {
            if (!(type.IsReadOnly)) 
            {
                type.SetReadOnly();
            }
            return type; 
        }
 
        // 
        // Type Description
        // 

        internal static string GetFullName(TypeUsage type)
        {
            return type.ToString(); 
        }
 
        internal static string GetFullName(EdmType type) 
        {
            return GetFullName(type.NamespaceName, type.Name); 
        }

        internal static string GetFullName(EntitySetBase entitySet)
        { 
            Debug.Assert(entitySet.EntityContainer != null, "entitySet.EntityContainer is null");
            return GetFullName(entitySet.EntityContainer.Name, entitySet.Name); 
        } 

        internal static string GetFullName(string qualifier, string name) 
        {
            if (string.IsNullOrEmpty(qualifier))
            {
                return string.Format(CultureInfo.InvariantCulture, "{0}", name); 
            }
            else 
            { 
                return string.Format(CultureInfo.InvariantCulture, "{0}.{1}", qualifier, name);
            } 
        }

        /// 
        /// Converts the given CLR type into a DbType 
        /// 
        /// The CLR type to convert 
        ///  
        internal static DbType ConvertClrTypeToDbType(Type clrType)
        { 
            switch (Type.GetTypeCode(clrType))
            {
                case TypeCode.Empty:
                    throw EntityUtil.InvalidDataType(TypeCode.Empty); 

                case TypeCode.Object: 
                    if (clrType == typeof(System.Byte[])) 
                    {
                        return DbType.Binary; 
                    }
                    if (clrType == typeof(System.Char[]))
                    {
                        // Always treat char and char[] as string 
                        return DbType.String;
                    } 
                    else if (clrType == typeof(System.Guid)) 
                    {
                        return DbType.Guid; 
                    }
                    else if (clrType == typeof(System.TimeSpan))
                    {
                        return DbType.Time; 
                    }
                    else if (clrType == typeof(System.DateTimeOffset)) 
                    { 
                        return DbType.DateTimeOffset;
                    } 

                    return DbType.Object;

                case TypeCode.DBNull: 
                    return DbType.Object;
                case TypeCode.Boolean: 
                    return DbType.Boolean; 
                case TypeCode.SByte:
                    return DbType.SByte; 
                case TypeCode.Byte:
                    return DbType.Byte;
                case TypeCode.Char:
                    // Always treat char and char[] as string 
                    return DbType.String;
                case TypeCode.Int16: 
                    return DbType.Int16; 
                case TypeCode.UInt16:
                    return DbType.UInt16; 
                case TypeCode.Int32:
                    return DbType.Int32;
                case TypeCode.UInt32:
                    return DbType.UInt32; 
                case TypeCode.Int64:
                    return DbType.Int64; 
                case TypeCode.UInt64: 
                    return DbType.UInt64;
                case TypeCode.Single: 
                    return DbType.Single;
                case TypeCode.Double:
                    return DbType.Double;
                case TypeCode.Decimal: 
                    return DbType.Decimal;
                case TypeCode.DateTime: 
                    return DbType.DateTime; 
                case TypeCode.String:
                    return DbType.String; 
                default:
                    throw EntityUtil.UnknownDataTypeCode(clrType, Type.GetTypeCode(clrType));
            }
        } 

        internal static bool IsIntegerConstant(TypeUsage valueType, object value, long expectedValue) 
        { 
            if (!TypeSemantics.IsIntegerNumericType(valueType))
            { 
                return false;
            }

            if (null == value) 
            {
                return false; 
            } 

            PrimitiveType intType = (PrimitiveType)valueType.EdmType; 
            switch (intType.PrimitiveTypeKind)
            {
                case PrimitiveTypeKind.Byte:
                    return (expectedValue == (byte)value); 

                case PrimitiveTypeKind.Int16: 
                    return (expectedValue == (short)value); 

                case PrimitiveTypeKind.Int32: 
                    return (expectedValue == (int)value);

                case PrimitiveTypeKind.Int64:
                    return (expectedValue == (long)value); 

                case PrimitiveTypeKind.SByte: 
                    return (expectedValue == (sbyte)value); 

                default: 
                    {
                        Debug.Assert(false, "Integer primitive type was not one of Byte, Int16, Int32, Int64, SByte?");
                        return false;
                    } 
            }
        } 
 
        /// 
        /// returns a Typeusage 
        /// 
        /// 
        /// 
        static internal TypeUsage GetLiteralTypeUsage(PrimitiveTypeKind primitiveTypeKind) 
        {
            // all clr strings by default are unicode 
            return GetLiteralTypeUsage(primitiveTypeKind, true /* unicode */); 
        }
 
        static internal TypeUsage GetLiteralTypeUsage(PrimitiveTypeKind primitiveTypeKind, bool isUnicode)
        {
            TypeUsage typeusage;
            PrimitiveType primitiveType = EdmProviderManifest.Instance.GetPrimitiveType(primitiveTypeKind); 
            switch (primitiveTypeKind)
            { 
                case PrimitiveTypeKind.String: 
                    typeusage = TypeUsage.Create(primitiveType,
                        new FacetValues{ Unicode = isUnicode, MaxLength = TypeUsage.DefaultMaxLengthFacetValue, FixedLength = false, Nullable = false}); 
                    break;

                default:
                    typeusage = TypeUsage.Create(primitiveType, 
                        new FacetValues{ Nullable = false });
                    break; 
            } 
            return typeusage;
        } 

        #endregion

        #region EdmFunction Helpers 
        internal static bool IsCanonicalFunction(EdmFunction function)
        { 
            bool isCanonicalFunction = (function.DataSpace == DataSpace.CSpace && function.NamespaceName == EdmConstants.EdmNamespace); 

            Debug.Assert(!isCanonicalFunction || (isCanonicalFunction && !function.HasUserDefinedBody), 
                "Canonical function '" + function.FullName + "' can not have a user defined body");

            return isCanonicalFunction;
        } 
        #endregion
    } 
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.


                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK