From f92de44c84de005a640dcab5e5da393985cd433f Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 23 Sep 2019 17:56:31 -0600 Subject: [PATCH 1/2] Start C# 8 refactor... --- ExchangeSharp.sln | 4 +- ExchangeSharp/API/Common/APIRequestMaker.cs | 40 ++++----- ExchangeSharp/API/Common/BaseAPI.cs | 58 ++++++------- ExchangeSharp/API/Common/IAPIRequestMaker.cs | 9 +- ExchangeSharp/API/Common/IBaseAPI.cs | 10 +-- .../Exchanges/Binance/ExchangeBinanceAPI.cs | 85 ++++++++++--------- .../API/Exchanges/Binance/Models/Currency.cs | 56 ++++++------ .../API/Exchanges/_Base/ExchangeAPI.cs | 57 +++++++------ .../Exchanges/_Base/ExchangeAPIExtensions.cs | 4 +- .../API/Exchanges/_Base/IExchangeAPI.cs | 8 +- ExchangeSharp/ExchangeSharp.csproj | 3 +- ExchangeSharp/Utility/CryptoUtility.cs | 56 ++++++------ ExchangeSharp/Utility/DataProtector.cs | 45 +++++----- ExchangeSharp/Utility/SignalrManager.cs | 13 +-- 14 files changed, 221 insertions(+), 227 deletions(-) diff --git a/ExchangeSharp.sln b/ExchangeSharp.sln index 1a85c129..99f23827 100644 --- a/ExchangeSharp.sln +++ b/ExchangeSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29318.209 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExchangeSharp", "ExchangeSharp\ExchangeSharp.csproj", "{B4ADDAEF-95BF-4FDA-8B2F-8B899EDA3F45}" EndProject diff --git a/ExchangeSharp/API/Common/APIRequestMaker.cs b/ExchangeSharp/API/Common/APIRequestMaker.cs index 04bb7fae..b772543a 100644 --- a/ExchangeSharp/API/Common/APIRequestMaker.cs +++ b/ExchangeSharp/API/Common/APIRequestMaker.cs @@ -33,7 +33,7 @@ private class InternalHttpWebRequest : IHttpWebRequest public InternalHttpWebRequest(Uri fullUri) { - request = HttpWebRequest.Create(fullUri) as HttpWebRequest; + request = (HttpWebRequest.Create(fullUri) as HttpWebRequest ?? throw new NullReferenceException("Failed to create HttpWebRequest")); request.KeepAlive = false; } @@ -145,22 +145,18 @@ public APIRequestMaker(IAPIRequestHandler api) /// The encoding of payload is API dependant but is typically json. /// Request method or null for default. Example: 'GET' or 'POST'. /// Raw response - public async Task MakeRequestAsync(string url, string baseUrl = null, Dictionary payload = null, string method = null) + public async Task MakeRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? method = null) { await new SynchronizationContextRemover(); - await api.RateLimit.WaitToProceedAsync(); - if (string.IsNullOrWhiteSpace(url)) - { - return null; - } - else if (url[0] != '/') + + if (url[0] != '/') { url = "/" + url; } string fullUrl = (baseUrl ?? api.BaseUrl) + url; - method = method ?? api.RequestMethod; + method ??= api.RequestMethod; Uri uri = api.ProcessRequestUrl(new UriBuilder(fullUrl), payload, method); InternalHttpWebRequest request = new InternalHttpWebRequest(uri) { @@ -171,8 +167,8 @@ public async Task MakeRequestAsync(string url, string baseUrl = null, Di request.AddHeader("user-agent", BaseAPI.RequestUserAgent); request.Timeout = request.ReadWriteTimeout = (int)api.RequestTimeout.TotalMilliseconds; await api.ProcessRequestAsync(request, payload); - HttpWebResponse response = null; - string responseString = null; + HttpWebResponse? response = null; + string responseString; try { @@ -194,21 +190,19 @@ public async Task MakeRequestAsync(string url, string baseUrl = null, Di } } using (Stream responseStream = response.GetResponseStream()) + using (StreamReader responseStreamReader = new StreamReader(responseStream)) + responseString = responseStreamReader.ReadToEnd(); + if (response.StatusCode != HttpStatusCode.OK) { - responseString = new StreamReader(responseStream).ReadToEnd(); - if (response.StatusCode != HttpStatusCode.OK) + // 404 maybe return empty responseString + if (string.IsNullOrWhiteSpace(responseString)) { - // 404 maybe return empty responseString - if (string.IsNullOrWhiteSpace(responseString)) - { - throw new APIException(string.Format("{0} - {1}", - response.StatusCode.ConvertInvariant(), response.StatusCode)); - } - throw new APIException(responseString); + throw new APIException(string.Format("{0} - {1}", response.StatusCode.ConvertInvariant(), response.StatusCode)); } - api.ProcessResponse(new InternalHttpWebResponse(response)); - RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString); + throw new APIException(responseString); } + api.ProcessResponse(new InternalHttpWebResponse(response)); + RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString); } catch (Exception ex) { @@ -225,6 +219,6 @@ public async Task MakeRequestAsync(string url, string baseUrl = null, Di /// /// An action to execute when a request has been made (this request and state and object (response or exception)) /// - public Action RequestStateChanged { get; set; } + public Action? RequestStateChanged { get; set; } } } diff --git a/ExchangeSharp/API/Common/BaseAPI.cs b/ExchangeSharp/API/Common/BaseAPI.cs index e47cc2f0..497fffab 100644 --- a/ExchangeSharp/API/Common/BaseAPI.cs +++ b/ExchangeSharp/API/Common/BaseAPI.cs @@ -148,28 +148,28 @@ public IAPIRequestMaker RequestMaker /// /// Base URL for the API for web sockets /// - public virtual string BaseUrlWebSocket { get; set; } + public virtual string BaseUrlWebSocket { get; set; } = string.Empty; /// /// Gets the name of the API /// - public virtual string Name { get; private set; } + public virtual string Name { get; private set; } = string.Empty; /// /// Public API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text! /// - public System.Security.SecureString PublicApiKey { get; set; } + public System.Security.SecureString? PublicApiKey { get; set; } /// /// Private API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text! /// - public System.Security.SecureString PrivateApiKey { get; set; } + public System.Security.SecureString? PrivateApiKey { get; set; } /// /// Pass phrase API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text! /// Most services do not require this, but Coinbase is an example of one that does /// - public System.Security.SecureString Passphrase { get; set; } + public System.Security.SecureString? Passphrase { get; set; } /// /// Rate limiter - set this to a new limit if you are seeing your ip get blocked by the API @@ -209,12 +209,12 @@ public IAPIRequestMaker RequestMaker /// /// The nonce end point for pulling down a server timestamp - override OnGetNonceOffset if you need custom handling /// - public string NonceEndPoint { get; protected set; } + public string? NonceEndPoint { get; protected set; } /// /// The field in the json returned by the nonce end point to parse out - override OnGetNonceOffset if you need custom handling /// - public string NonceEndPointField { get; protected set; } + public string? NonceEndPointField { get; protected set; } /// /// The type of value in the nonce end point field - override OnGetNonceOffset if you need custom handling. @@ -297,7 +297,7 @@ public BaseAPI() } else { - Name = (nameAttributes[0] as ApiNameAttribute).Name; + Name = (nameAttributes[0] as ApiNameAttribute)?.Name ?? string.Empty; } } @@ -376,7 +376,7 @@ public async Task GenerateNonceAsync() { // why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window... // making it required to increment by 1 is also a pain - especially when restarting a process or rebooting. - string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce"); + string tempFile = Path.Combine(Path.GetTempPath(), (PublicApiKey?.ToUnsecureString() ?? "unknown_pub_key") + ".nonce"); if (!File.Exists(tempFile)) { File.WriteAllText(tempFile, "0"); @@ -449,7 +449,7 @@ public void LoadAPIKeys(string encryptedFile) /// Public Api Key /// Private Api Key /// Pass phrase, null for none - public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, string passPhrase = null) + public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, string? passPhrase = null) { PublicApiKey = publicApiKey.ToSecureString(); PrivateApiKey = privateApiKey.ToSecureString(); @@ -465,7 +465,7 @@ public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, strin /// The encoding of payload is API dependant but is typically json. /// Request method or null for default /// Raw response - public Task MakeRequestAsync(string url, string baseUrl = null, Dictionary payload = null, string method = null) => requestMaker.MakeRequestAsync(url, baseUrl: baseUrl, payload: payload, method: method); + public Task MakeRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? method = null) => requestMaker.MakeRequestAsync(url, baseUrl: baseUrl, payload: payload, method: method); /// /// Make a JSON request to an API end point @@ -476,7 +476,7 @@ public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, strin /// Payload, can be null. For private API end points, the payload must contain a 'nonce' key set to GenerateNonce value. /// Request method or null for default /// Result decoded from JSON response - public async Task MakeJsonRequestAsync(string url, string baseUrl = null, Dictionary payload = null, string requestMethod = null) + public async Task MakeJsonRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? requestMethod = null) { await new SynchronizationContextRemover(); @@ -500,8 +500,8 @@ public Task ConnectWebSocketAsync ( string url, Func messageCallback, - WebSocketConnectionDelegate connectCallback = null, - WebSocketConnectionDelegate disconnectCallback = null + WebSocketConnectionDelegate? connectCallback = null, + WebSocketConnectionDelegate? disconnectCallback = null ) { if (messageCallback == null) @@ -532,7 +532,7 @@ public Task ConnectWebSocketAsync /// /// Payload to potentially send /// True if an authenticated request can be made with the payload, false otherwise - protected virtual bool CanMakeAuthenticatedRequest(IReadOnlyDictionary payload) + protected virtual bool CanMakeAuthenticatedRequest(IReadOnlyDictionary? payload) { return (PrivateApiKey != null && PublicApiKey != null && payload != null && payload.ContainsKey("nonce")); } @@ -543,7 +543,7 @@ protected virtual bool CanMakeAuthenticatedRequest(IReadOnlyDictionary /// Request /// Payload - protected virtual Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + protected virtual Task ProcessRequestAsync(IHttpWebRequest request, Dictionary? payload) { return Task.CompletedTask; } @@ -564,7 +564,7 @@ protected virtual void ProcessResponse(IHttpWebResponse response) /// Payload /// Method /// Updated url - protected virtual Uri ProcessRequestUrl(UriBuilder url, Dictionary payload, string method) + protected virtual Uri ProcessRequestUrl(UriBuilder url, Dictionary? payload, string? method) { return url.Uri; } @@ -647,22 +647,14 @@ protected virtual async Task OnGetNonceOffset() try { - JToken token = await MakeJsonRequestAsync(NonceEndPoint); + JToken token = await MakeJsonRequestAsync(NonceEndPoint!); JToken value = token[NonceEndPointField]; - DateTime serverDate; - switch (NonceEndPointStyle) + DateTime serverDate = NonceEndPointStyle switch { - case NonceStyle.Iso8601: - serverDate = value.ToDateTimeInvariant(); - break; - - case NonceStyle.UnixMilliseconds: - serverDate = value.ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(); - break; - - default: - throw new ArgumentException("Invalid nonce end point style '" + NonceEndPointStyle + "' for exchange '" + Name + "'"); - } + NonceStyle.Iso8601 => value.ToDateTimeInvariant(), + NonceStyle.UnixMilliseconds => value.ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(), + _ => throw new ArgumentException("Invalid nonce end point style '" + NonceEndPointStyle + "' for exchange '" + Name + "'"), + }; NonceOffset = (CryptoUtility.UtcNow - serverDate); } catch @@ -672,7 +664,7 @@ protected virtual async Task OnGetNonceOffset() } } - async Task IAPIRequestHandler.ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + async Task IAPIRequestHandler.ProcessRequestAsync(IHttpWebRequest request, Dictionary? payload) { await ProcessRequestAsync(request, payload); } @@ -682,7 +674,7 @@ void IAPIRequestHandler.ProcessResponse(IHttpWebResponse response) ProcessResponse(response); } - Uri IAPIRequestHandler.ProcessRequestUrl(UriBuilder url, Dictionary payload, string method) + Uri IAPIRequestHandler.ProcessRequestUrl(UriBuilder url, Dictionary? payload, string? method) { return ProcessRequestUrl(url, payload, method); } diff --git a/ExchangeSharp/API/Common/IAPIRequestMaker.cs b/ExchangeSharp/API/Common/IAPIRequestMaker.cs index 11b144a6..67a77938 100644 --- a/ExchangeSharp/API/Common/IAPIRequestMaker.cs +++ b/ExchangeSharp/API/Common/IAPIRequestMaker.cs @@ -52,12 +52,13 @@ public interface IAPIRequestMaker /// The encoding of payload is API dependant but is typically json. /// Request method or null for default /// Raw response - Task MakeRequestAsync(string url, string baseUrl = null, Dictionary payload = null, string method = null); + /// Request fails + Task MakeRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? method = null); /// /// An action to execute when a request has been made (this request and state and object (response or exception)) /// - Action RequestStateChanged { get; set; } + Action? RequestStateChanged { get; set; } } /// @@ -130,7 +131,7 @@ public interface IAPIRequestHandler /// /// Request /// Payload - Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload); + Task ProcessRequestAsync(IHttpWebRequest request, Dictionary? payload); /// /// Additional handling for response @@ -145,7 +146,7 @@ public interface IAPIRequestHandler /// Payload /// Method /// Updated url - Uri ProcessRequestUrl(UriBuilder url, Dictionary payload, string method); + Uri ProcessRequestUrl(UriBuilder url, Dictionary? payload, string method); /// /// Base url for the request diff --git a/ExchangeSharp/API/Common/IBaseAPI.cs b/ExchangeSharp/API/Common/IBaseAPI.cs index 21f68019..3a3a6dc7 100644 --- a/ExchangeSharp/API/Common/IBaseAPI.cs +++ b/ExchangeSharp/API/Common/IBaseAPI.cs @@ -38,18 +38,18 @@ public interface IBaseAPI : INamed /// /// Optional public API key /// - SecureString PublicApiKey { get; set; } + SecureString? PublicApiKey { get; set; } /// /// Optional private API key /// - SecureString PrivateApiKey { get; set; } + SecureString? PrivateApiKey { get; set; } /// /// Pass phrase API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text! /// Most exchanges do not require this, but Coinbase is an example of one that does /// - System.Security.SecureString Passphrase { get; set; } + System.Security.SecureString? Passphrase { get; set; } /// /// Request timeout @@ -92,7 +92,7 @@ public interface IBaseAPI : INamed /// Private Api Key /// Pass phrase, null for none /// - void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, string passPhrase = null); + void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, string? passPhrase = null); /// /// Generate a nonce @@ -109,7 +109,7 @@ public interface IBaseAPI : INamed /// Payload, can be null. For private API end points, the payload must contain a 'nonce' key set to GenerateNonce value. /// Request method or null for default /// Result decoded from JSON response - Task MakeJsonRequestAsync(string url, string baseUrl = null, Dictionary payload = null, string requestMethod = null); + Task MakeJsonRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? requestMethod = null); #endregion Methods } diff --git a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs index 5b8d2c1c..0ad7b99f 100644 --- a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs +++ b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs @@ -96,7 +96,7 @@ public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(strin /// /// Symbol to get trades for or null for all /// All trades for the specified symbol, or all if null symbol - public async Task> GetMyTradesAsync(string marketSymbol = null, DateTime? afterDate = null) + public async Task> GetMyTradesAsync(string? marketSymbol = null, DateTime? afterDate = null) { await new SynchronizationContextRemover(); return await OnGetMyTradesAsync(marketSymbol, afterDate); @@ -105,11 +105,14 @@ public async Task> GetMyTradesAsync(string mark protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); - JToken obj = await MakeJsonRequestAsync("/ticker/allPrices"); - foreach (JToken token in obj) - { - symbols.Add(token["symbol"].ToStringInvariant()); - } + JToken? obj = await MakeJsonRequestAsync("/ticker/allPrices"); + if (!(obj is null)) + { + foreach (JToken token in obj) + { + symbols.Add(token["symbol"].ToStringInvariant()); + } + } return symbols; } @@ -169,7 +172,7 @@ protected override async Task> OnGetMarketSymbolsMet // "LOT_SIZE" JToken filters = marketSymbolToken["filters"]; - JToken lotSizeFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "LOT_SIZE")); + JToken? lotSizeFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "LOT_SIZE")); if (lotSizeFilter != null) { market.MaxTradeSize = lotSizeFilter["maxQty"].ConvertInvariant(); @@ -178,7 +181,7 @@ protected override async Task> OnGetMarketSymbolsMet } // PRICE_FILTER - JToken priceFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "PRICE_FILTER")); + JToken? priceFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "PRICE_FILTER")); if (priceFilter != null) { market.MaxPrice = priceFilter["maxPrice"].ConvertInvariant(); @@ -187,7 +190,7 @@ protected override async Task> OnGetMarketSymbolsMet } // MIN_NOTIONAL - JToken minNotionalFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "MIN_NOTIONAL")); + JToken? minNotionalFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "MIN_NOTIONAL")); if (minNotionalFilter != null) { market.MinTradeSizeInQuoteCurrency = minNotionalFilter["minNotional"].ConvertInvariant(); @@ -500,25 +503,29 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd } order.ExtraParameters.CopyTo(payload); - JToken token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "POST"); + JToken? token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "POST"); + if (token is null) + { + return null; + } return ParseOrder(token); } - protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string? marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); - if (string.IsNullOrEmpty(marketSymbol)) + if (string.IsNullOrWhiteSpace(marketSymbol)) { throw new InvalidOperationException("Binance single order details request requires symbol"); } - payload["symbol"] = marketSymbol; + payload["symbol"] = marketSymbol!; payload["orderId"] = orderId; JToken token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload); ExchangeOrderResult result = ParseOrder(token); // Add up the fees from each trade in the order Dictionary feesPayload = await GetNoncePayloadAsync(); - feesPayload["symbol"] = marketSymbol; + feesPayload["symbol"] = marketSymbol!; JToken feesToken = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, feesPayload); ParseFees(feesToken, result); @@ -546,13 +553,13 @@ private static void ParseFees(JToken feesToken, ExchangeOrderResult result) } } - protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string? marketSymbol = null) { List orders = new List(); Dictionary payload = await GetNoncePayloadAsync(); if (!string.IsNullOrWhiteSpace(marketSymbol)) { - payload["symbol"] = marketSymbol; + payload["symbol"] = marketSymbol!; } JToken token = await MakeJsonRequestAsync("/openOrders", BaseUrlPrivate, payload); foreach (JToken order in token) @@ -567,8 +574,8 @@ private async Task> GetCompletedOrdersForAllSym { // TODO: This is a HACK, Binance API needs to add a single API call to get all orders for all symbols, terrible... List orders = new List(); - Exception ex = null; - string failedSymbol = null; + Exception? ex = null; + string? failedSymbol = null; Parallel.ForEach((await GetMarketSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => { try @@ -601,7 +608,7 @@ private async Task> GetCompletedOrdersForAllSym return orders; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string? marketSymbol = null, DateTime? afterDate = null) { //new way List trades = new List(); @@ -612,7 +619,7 @@ protected override async Task> OnGetCompletedOr else { Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = marketSymbol; + payload["symbol"] = marketSymbol!; if (afterDate != null) { payload["startTime"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds(); @@ -620,7 +627,7 @@ protected override async Task> OnGetCompletedOr JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, payload); foreach (JToken trade in token) { - trades.Add(ParseTrade(trade, marketSymbol)); + trades.Add(ParseTrade(trade, marketSymbol!)); } } return trades; @@ -653,8 +660,8 @@ private async Task> GetMyTradesForAllSymbols(Da { // TODO: This is a HACK, Binance API needs to add a single API call to get all orders for all symbols, terrible... List trades = new List(); - Exception ex = null; - string failedSymbol = null; + Exception? ex = null; + string? failedSymbol = null; Parallel.ForEach((await GetMarketSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => { try @@ -687,7 +694,7 @@ private async Task> GetMyTradesForAllSymbols(Da return trades; } - private async Task> OnGetMyTradesAsync(string marketSymbol = null, DateTime? afterDate = null) + private async Task> OnGetMyTradesAsync(string? marketSymbol = null, DateTime? afterDate = null) { List trades = new List(); if (string.IsNullOrWhiteSpace(marketSymbol)) @@ -697,7 +704,7 @@ private async Task> OnGetMyTradesAsync(string m else { Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = marketSymbol; + payload["symbol"] = marketSymbol!; if (afterDate != null) { payload["timestamp"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds(); @@ -705,22 +712,22 @@ private async Task> OnGetMyTradesAsync(string m JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, payload); foreach (JToken trade in token) { - trades.Add(ParseTrade(trade, marketSymbol)); + trades.Add(ParseTrade(trade, marketSymbol!)); } } return trades; } - protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string? marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); if (string.IsNullOrWhiteSpace(marketSymbol)) { throw new InvalidOperationException("Binance cancel order request requires symbol"); } - payload["symbol"] = marketSymbol; + payload["symbol"] = marketSymbol!; payload["orderId"] = orderId; - JToken token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "DELETE"); + _ = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "DELETE"); } /// A withdrawal request. Fee is automatically subtracted from the amount. @@ -950,27 +957,23 @@ private void ParseAveragePriceAndFeesFromFills(ExchangeOrderResult result, JToke result.AveragePrice = (totalQuantity == 0 ? 0 : totalCost / totalQuantity); } - protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary? payload) { - if (CanMakeAuthenticatedRequest(payload)) - { - request.AddHeader("X-MBX-APIKEY", PublicApiKey.ToUnsecureString()); - } - // Needed in order to get listening key - if (payload == null && request.RequestUri.AbsoluteUri.Contains("userDataStream")) - { - request.AddHeader("X-MBX-APIKEY", PublicApiKey.ToUnsecureString()); - } + if (CanMakeAuthenticatedRequest(payload) || + (payload == null && request.RequestUri.AbsoluteUri.Contains("userDataStream"))) + { + request.AddHeader("X-MBX-APIKEY", PublicApiKey!.ToUnsecureString()); + } return base.ProcessRequestAsync(request, payload); } - protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary payload, string method) + protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary? payload, string? method) { if (CanMakeAuthenticatedRequest(payload)) { // payload is ignored, except for the nonce which is added to the url query - bittrex puts all the "post" parameters in the url query instead of the request body var query = (url.Query ?? string.Empty).Trim('?', '&'); - string newQuery = "timestamp=" + payload["nonce"].ToStringInvariant() + (query.Length != 0 ? "&" + query : string.Empty) + + string newQuery = "timestamp=" + payload!["nonce"].ToStringInvariant() + (query.Length != 0 ? "&" + query : string.Empty) + (payload.Count > 1 ? "&" + CryptoUtility.GetFormForPayload(payload, false) : string.Empty); string signature = CryptoUtility.SHA256Sign(newQuery, CryptoUtility.ToUnsecureBytesUTF8(PrivateApiKey)); newQuery += "&signature=" + signature; diff --git a/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs b/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs index 95ecb759..0902faec 100644 --- a/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs +++ b/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs @@ -17,16 +17,16 @@ namespace ExchangeSharp.Binance internal class Currency { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("assetCode")] - public string AssetCode { get; set; } + public string? AssetCode { get; set; } [JsonProperty("assetName")] - public string AssetName { get; set; } + public string? AssetName { get; set; } [JsonProperty("unit")] - public string Unit { get; set; } + public string? Unit { get; set; } [JsonProperty("transactionFee")] public decimal TransactionFee { get; set; } @@ -41,25 +41,25 @@ internal class Currency public long FreeUserChargeAmount { get; set; } [JsonProperty("minProductWithdraw")] - public string MinProductWithdraw { get; set; } + public string? MinProductWithdraw { get; set; } [JsonProperty("withdrawIntegerMultiple")] - public string WithdrawIntegerMultiple { get; set; } + public string? WithdrawIntegerMultiple { get; set; } [JsonProperty("confirmTimes")] - public string ConfirmTimes { get; set; } + public string? ConfirmTimes { get; set; } [JsonProperty("chargeLockConfirmTimes")] - public string ChargeLockConfirmTimes { get; set; } + public string? ChargeLockConfirmTimes { get; set; } [JsonProperty("url")] - public string Url { get; set; } + public string? Url { get; set; } [JsonProperty("addressUrl")] - public string AddressUrl { get; set; } + public string? AddressUrl { get; set; } [JsonProperty("blockUrl")] - public string BlockUrl { get; set; } + public string? BlockUrl { get; set; } [JsonProperty("enableCharge")] public bool EnableCharge { get; set; } @@ -68,16 +68,16 @@ internal class Currency public bool EnableWithdraw { get; set; } [JsonProperty("regEx")] - public string RegEx { get; set; } + public string? RegEx { get; set; } [JsonProperty("regExTag")] - public string RegExTag { get; set; } + public string? RegExTag { get; set; } [JsonProperty("gas")] public decimal Gas { get; set; } [JsonProperty("parentCode")] - public string ParentCode { get; set; } + public string? ParentCode { get; set; } [JsonProperty("isLegalMoney")] public bool IsLegalMoney { get; set; } @@ -86,22 +86,22 @@ internal class Currency public decimal ReconciliationAmount { get; set; } [JsonProperty("seqNum")] - public string SeqNum { get; set; } + public string? SeqNum { get; set; } [JsonProperty("chineseName")] - public string ChineseName { get; set; } + public string? ChineseName { get; set; } [JsonProperty("cnLink")] - public string CnLink { get; set; } + public string? CnLink { get; set; } [JsonProperty("enLink")] - public string EnLink { get; set; } + public string? EnLink { get; set; } [JsonProperty("logoUrl")] - public string LogoUrl { get; set; } + public string? LogoUrl { get; set; } [JsonProperty("fullLogoUrl")] - public string FullLogoUrl { get; set; } + public string? FullLogoUrl { get; set; } [JsonProperty("forceStatus")] public bool ForceStatus { get; set; } @@ -110,13 +110,13 @@ internal class Currency public bool ResetAddressStatus { get; set; } [JsonProperty("chargeDescCn")] - public object ChargeDescCn { get; set; } + public object? ChargeDescCn { get; set; } [JsonProperty("chargeDescEn")] - public object ChargeDescEn { get; set; } + public object? ChargeDescEn { get; set; } [JsonProperty("assetLabel")] - public object AssetLabel { get; set; } + public object? AssetLabel { get; set; } [JsonProperty("sameAddress")] public bool SameAddress { get; set; } @@ -128,19 +128,19 @@ internal class Currency public bool DynamicFeeStatus { get; set; } [JsonProperty("depositTipEn")] - public object DepositTipEn { get; set; } + public object? DepositTipEn { get; set; } [JsonProperty("depositTipCn")] - public object DepositTipCn { get; set; } + public object? DepositTipCn { get; set; } [JsonProperty("assetLabelEn")] - public object AssetLabelEn { get; set; } + public object? AssetLabelEn { get; set; } [JsonProperty("supportMarket")] - public object SupportMarket { get; set; } + public object? SupportMarket { get; set; } [JsonProperty("feeReferenceAsset")] - public string FeeReferenceAsset { get; set; } + public string? FeeReferenceAsset { get; set; } [JsonProperty("feeRate")] public decimal? FeeRate { get; set; } diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs index 3da6f4aa..d9b6c1a3 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs @@ -43,7 +43,7 @@ public abstract partial class ExchangeAPI : BaseAPI, IExchangeAPI #region Private methods - private static readonly Dictionary apis = new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary apis = new Dictionary(StringComparer.OrdinalIgnoreCase); private bool disposed; #endregion Private methods @@ -100,10 +100,10 @@ await GetHistoricalTradesAsync((e) => protected virtual Task> OnGetAmountsAvailableToTradeAsync() => throw new NotImplementedException(); protected virtual Task OnPlaceOrderAsync(ExchangeOrderRequest order) => throw new NotImplementedException(); protected virtual Task OnPlaceOrdersAsync(params ExchangeOrderRequest[] order) => throw new NotImplementedException(); - protected virtual Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) => throw new NotImplementedException(); - protected virtual Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) => throw new NotImplementedException(); - protected virtual Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) => throw new NotImplementedException(); - protected virtual Task OnCancelOrderAsync(string orderId, string marketSymbol = null) => throw new NotImplementedException(); + protected virtual Task OnGetOrderDetailsAsync(string orderId, string? marketSymbol = null) => throw new NotImplementedException(); + protected virtual Task> OnGetOpenOrderDetailsAsync(string? marketSymbol = null) => throw new NotImplementedException(); + protected virtual Task> OnGetCompletedOrderDetailsAsync(string? marketSymbol = null, DateTime? afterDate = null) => throw new NotImplementedException(); + protected virtual Task OnCancelOrderAsync(string orderId, string? marketSymbol = null) => throw new NotImplementedException(); protected virtual Task OnWithdrawAsync(ExchangeWithdrawalRequest withdrawalRequest) => throw new NotImplementedException(); protected virtual Task> OnGetWithdrawHistoryAsync(string currency) => throw new NotImplementedException(); protected virtual Task> OnGetMarginAmountsAvailableToTradeAsync(bool includeZeroBalances) => throw new NotImplementedException(); @@ -205,8 +205,9 @@ static ExchangeAPI() // we don't want to pro-actively create all of these becanse an API // may be running a timer or other house-keeping which we don't want // the overhead of if a user is only using one or a handful of the apis - using (ExchangeAPI api = Activator.CreateInstance(type) as ExchangeAPI) + if ((Activator.CreateInstance(type) is ExchangeAPI api)) { + api.Dispose(); apis[api.Name] = null; } @@ -274,13 +275,13 @@ public void Dispose() /// /// Exchange name /// Exchange API or null if not found - public static IExchangeAPI GetExchangeAPI(string exchangeName) + public static IExchangeAPI? GetExchangeAPI(string exchangeName) { // note: this method will be slightly slow (milliseconds) the first time it is called and misses the cache // subsequent calls with cache hits will be nanoseconds lock (apis) { - if (!apis.TryGetValue(exchangeName, out IExchangeAPI api)) + if (!apis.TryGetValue(exchangeName, out IExchangeAPI? api)) { throw new ArgumentException("No API available with name " + exchangeName); } @@ -289,20 +290,22 @@ public static IExchangeAPI GetExchangeAPI(string exchangeName) // find an API with the right name foreach (Type type in typeof(ExchangeAPI).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ExchangeAPI)) && !type.IsAbstract)) { - api = Activator.CreateInstance(type) as IExchangeAPI; - if (api.Name == exchangeName) + if (!((api = Activator.CreateInstance(type) as IExchangeAPI) is null)) { - // found one with right name, add it to the API dictionary - apis[exchangeName] = api; - - // break out, we are done - break; - } - else - { - // name didn't match, dispose immediately to stop timers and other nasties we don't want running, and null out api variable - api.Dispose(); - api = null; + if (api.Name == exchangeName) + { + // found one with right name, add it to the API dictionary + apis[exchangeName] = api; + + // break out, we are done + break; + } + else + { + // name didn't match, dispose immediately to stop timers and other nasties we don't want running, and null out api variable + api.Dispose(); + api = null; + } } } } @@ -375,7 +378,7 @@ public string GlobalCurrencyToExchangeCurrency(string currency) /// /// Symbol /// Normalized symbol - public virtual string NormalizeMarketSymbol(string marketSymbol) + public virtual string NormalizeMarketSymbol(string? marketSymbol) { marketSymbol = (marketSymbol ?? string.Empty).Trim(); marketSymbol = marketSymbol.Replace("-", MarketSymbolSeparator) @@ -531,7 +534,7 @@ public virtual async Task> GetMarketSymbolsMetadataA /// /// The market symbol. Ex. ADA/BTC. This is assumed to be normalized and already correct for the exchange. /// The ExchangeMarket or null if it doesn't exist in the cache or there was an error - public virtual async Task GetExchangeMarketFromCacheAsync(string marketSymbol) + public virtual async Task GetExchangeMarketFromCacheAsync(string marketSymbol) { try { @@ -735,7 +738,7 @@ public virtual async Task PlaceOrdersAsync(params Exchang /// Order id to get details for /// Symbol of order (most exchanges do not require this) /// Order details - public virtual async Task GetOrderDetailsAsync(string orderId, string marketSymbol = null) + public virtual async Task GetOrderDetailsAsync(string orderId, string? marketSymbol = null) { marketSymbol = NormalizeMarketSymbol(marketSymbol); return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOrderDetailsAsync(orderId, marketSymbol), nameof(GetOrderDetailsAsync), nameof(orderId), orderId, nameof(marketSymbol), marketSymbol); @@ -746,7 +749,7 @@ public virtual async Task GetOrderDetailsAsync(string order /// /// Symbol to get open orders for or null for all /// All open order details - public virtual async Task> GetOpenOrderDetailsAsync(string marketSymbol = null) + public virtual async Task> GetOpenOrderDetailsAsync(string? marketSymbol = null) { marketSymbol = NormalizeMarketSymbol(marketSymbol); return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOpenOrderDetailsAsync(marketSymbol), nameof(GetOpenOrderDetailsAsync), nameof(marketSymbol), marketSymbol); @@ -758,7 +761,7 @@ public virtual async Task> GetOpenOrderDetailsA /// Symbol to get completed orders for or null for all /// Only returns orders on or after the specified date/time /// All completed order details for the specified symbol, or all if null symbol - public virtual async Task> GetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) + public virtual async Task> GetCompletedOrderDetailsAsync(string? marketSymbol = null, DateTime? afterDate = null) { marketSymbol = NormalizeMarketSymbol(marketSymbol); return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetCompletedOrderDetailsAsync(marketSymbol, afterDate)).ToArray(), nameof(GetCompletedOrderDetailsAsync), @@ -770,7 +773,7 @@ public virtual async Task> GetCompletedOrderDet /// /// Order id of the order to cancel /// Symbol of order (most exchanges do not require this) - public virtual async Task CancelOrderAsync(string orderId, string marketSymbol = null) + public virtual async Task CancelOrderAsync(string orderId, string? marketSymbol = null) { // *NOTE* do not wrap in CacheMethodCall await new SynchronizationContextRemover(); diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs index c395fdaf..7aaabf3a 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs @@ -50,7 +50,7 @@ public static async Task GetFullOrderBookWebSocketAsync(this IOrderB ConcurrentDictionary fullBooks = new ConcurrentDictionary(); Dictionary> partialOrderBookQueues = new Dictionary>(); - void applyDelta(SortedDictionary deltaValues, SortedDictionary bookToEdit) + static void applyDelta(SortedDictionary deltaValues, SortedDictionary bookToEdit) { foreach (ExchangeOrderPrice record in deltaValues.Values) { @@ -65,7 +65,7 @@ void applyDelta(SortedDictionary deltaValues, Sorte } } - void updateOrderBook(ExchangeOrderBook fullOrderBook, ExchangeOrderBook freshBook) + static void updateOrderBook(ExchangeOrderBook fullOrderBook, ExchangeOrderBook freshBook) { lock (fullOrderBook) { diff --git a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs index dab28e57..a2130b85 100644 --- a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs @@ -164,14 +164,14 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// order id /// Market Symbol /// Order details - Task GetOrderDetailsAsync(string orderId, string marketSymbol = null); + Task GetOrderDetailsAsync(string orderId, string? marketSymbol = null); /// /// Get the details of all open orders /// /// Market symbol to get open orders for or null for all /// All open order details for the specified symbol - Task> GetOpenOrderDetailsAsync(string marketSymbol = null); + Task> GetOpenOrderDetailsAsync(string? marketSymbol = null); /// /// Get the details of all completed orders @@ -179,14 +179,14 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Market symbol to get completed orders for or null for all /// Only returns orders on or after the specified date/time /// All completed order details for the specified symbol, or all if null symbol - Task> GetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null); + Task> GetCompletedOrderDetailsAsync(string? marketSymbol = null, DateTime? afterDate = null); /// /// Cancel an order, an exception is thrown if failure /// /// Order id of the order to cancel /// Market symbol of the order to cancel (not required for most exchanges) - Task CancelOrderAsync(string orderId, string marketSymbol = null); + Task CancelOrderAsync(string orderId, string? marketSymbol = null); /// /// Get margin amounts available to trade, symbol / amount dictionary diff --git a/ExchangeSharp/ExchangeSharp.csproj b/ExchangeSharp/ExchangeSharp.csproj index e47b09b8..809c02cf 100644 --- a/ExchangeSharp/ExchangeSharp.csproj +++ b/ExchangeSharp/ExchangeSharp.csproj @@ -1,11 +1,12 @@  - net472;netstandard2.0;netcoreapp2.2 + net472;netstandard2.0;netstandard2.1 Copyright 2017, Digital Ruby, LLC - www.digitalruby.com false true latest + enable DigitalRuby.ExchangeSharp ExchangeSharp - C# API for cryptocurrency exchanges 0.6.1 diff --git a/ExchangeSharp/Utility/CryptoUtility.cs b/ExchangeSharp/Utility/CryptoUtility.cs index 490da8d8..9b57bedc 100644 --- a/ExchangeSharp/Utility/CryptoUtility.cs +++ b/ExchangeSharp/Utility/CryptoUtility.cs @@ -72,7 +72,7 @@ public static void SetDateTimeUtcNowFunc(Func utcNowFunc) /// Object /// Parameter name /// Message - public static void ThrowIfNull(this object obj, string name, string message = null) + public static void ThrowIfNull(this object obj, string? name, string? message = null) { if (obj == null) { @@ -86,7 +86,7 @@ public static void ThrowIfNull(this object obj, string name, string message = nu /// Object /// Parameter name /// Message - public static void ThrowIfNullOrWhitespace(this string obj, string name, string message = null) + public static void ThrowIfNullOrWhitespace(this string obj, string name, string? message = null) { if (string.IsNullOrWhiteSpace(obj)) { @@ -289,7 +289,7 @@ public static string ToUnsecureString(this SecureString s) /// /// SecureString /// Binary data - public static byte[] ToUnsecureBytesUTF8(this SecureString s) + public static byte[]? ToUnsecureBytesUTF8(this SecureString? s) { if (s == null) { @@ -303,7 +303,7 @@ public static byte[] ToUnsecureBytesUTF8(this SecureString s) /// /// SecureString in base64 format /// Binary data - public static byte[] ToBytesBase64Decode(this SecureString s) + public static byte[]? ToBytesBase64Decode(this SecureString? s) { if (s == null) { @@ -1075,37 +1075,39 @@ public static byte[] AesEncryption(byte[] input, byte[] password, byte[] salt) /// Password /// Salt /// Decrypted data - public static byte[] AesDecryption(byte[] input, byte[] password, byte[] salt) + public static byte[]? AesDecryption(byte[] input, byte[] password, byte[] salt) { if (input == null || input.Length == 0 || password == null || password.Length == 0 || salt == null || salt.Length == 0) { return null; } MemoryStream decrypted = new MemoryStream(); - var AES = new RijndaelManaged() + using (RijndaelManaged AES = new RijndaelManaged() { KeySize = 256, BlockSize = 128, Padding = PaddingMode.PKCS7, - }; - var key = new Rfc2898DeriveBytes(password, salt, 1024); - AES.Key = key.GetBytes(AES.KeySize / 8); - AES.IV = key.GetBytes(AES.BlockSize / 8); - AES.Mode = CipherMode.CBC; - MemoryStream encrypted = new MemoryStream(input); - byte[] saltMatch = new byte[salt.Length]; - if (encrypted.Read(saltMatch, 0, saltMatch.Length) != salt.Length || !salt.SequenceEqual(saltMatch)) - { - throw new InvalidOperationException("Invalid salt"); - } - var cs = new CryptoStream(encrypted, AES.CreateDecryptor(), CryptoStreamMode.Read); - byte[] buffer = new byte[8192]; - int count; - while ((count = cs.Read(buffer, 0, buffer.Length)) > 0) - { - decrypted.Write(buffer, 0, count); + }) + { + var key = new Rfc2898DeriveBytes(password, salt, 1024); + AES.Key = key.GetBytes(AES.KeySize / 8); + AES.IV = key.GetBytes(AES.BlockSize / 8); + AES.Mode = CipherMode.CBC; + MemoryStream encrypted = new MemoryStream(input); + byte[] saltMatch = new byte[salt.Length]; + if (encrypted.Read(saltMatch, 0, saltMatch.Length) != salt.Length || !salt.SequenceEqual(saltMatch)) + { + throw new InvalidOperationException("Invalid salt"); + } + var cs = new CryptoStream(encrypted, AES.CreateDecryptor(), CryptoStreamMode.Read); + byte[] buffer = new byte[8192]; + int count; + while ((count = cs.Read(buffer, 0, buffer.Length)) > 0) + { + decrypted.Write(buffer, 0, count); + } + return decrypted.ToArray(); } - return decrypted.ToArray(); } /// @@ -1362,7 +1364,7 @@ public static T Sync(this Task task) /// Method implementation /// Function arguments - function name and then param name, value, name, value, etc. /// - public static async Task CacheMethod(this ICache cache, Dictionary methodCachePolicy, Func> method, params object[] arguments) where T : class + public static async Task CacheMethod(this ICache cache, Dictionary methodCachePolicy, Func> method, params object?[] arguments) where T : class { await new SynchronizationContextRemover(); methodCachePolicy.ThrowIfNull(nameof(methodCachePolicy)); @@ -1370,11 +1372,11 @@ public static async Task CacheMethod(this ICache cache, Dictionary /// Data to protect /// Protected data - public static byte[] Protect(byte[] data, byte[] optionalEntropy = null, DataProtectionScope scope = DataProtectionScope.CurrentUser) + public static byte[] Protect(byte[] data, byte[]? optionalEntropy = null, DataProtectionScope scope = DataProtectionScope.CurrentUser) { if (CryptoUtility.IsWindows) { @@ -520,7 +517,7 @@ public static byte[] Protect(byte[] data, byte[] optionalEntropy = null, DataPro /// /// Data to unprotect /// Unprotected data - public static byte[] Unprotect(byte[] data, byte[] optionalEntropy = null, DataProtectionScope scope = DataProtectionScope.CurrentUser) + public static byte[] Unprotect(byte[] data, byte[]? optionalEntropy = null, DataProtectionScope scope = DataProtectionScope.CurrentUser) { if (CryptoUtility.IsWindows) { diff --git a/ExchangeSharp/Utility/SignalrManager.cs b/ExchangeSharp/Utility/SignalrManager.cs index 51c3be8b..1ada8625 100644 --- a/ExchangeSharp/Utility/SignalrManager.cs +++ b/ExchangeSharp/Utility/SignalrManager.cs @@ -103,14 +103,14 @@ public SignalrSocketConnection(SignalrManager manager) /// Delay after invoking each object[] in param, used if the server will disconnect you for too many invoke too fast /// End point parameters, each array of strings is a separate call to the end point function. For no parameters, pass null. /// Connection - public async Task OpenAsync(string functionName, Func callback, int delayMilliseconds = 0, object[][] param = null) + public async Task OpenAsync(string functionName, Func callback, int delayMilliseconds = 0, object[][]? param = null) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); SignalrManager _manager = this.manager; _manager.ThrowIfNull(nameof(manager), "Manager is null"); - Exception ex = null; + Exception? ex = null; param = (param ?? new object[][] { new object[0] }); string functionFullName = _manager.GetFunctionFullName(functionName); this.functionFullName = functionFullName; @@ -257,9 +257,9 @@ public sealed class WebsocketCustomTransport : ClientTransportBase public WebsocketCustomTransport(IHttpClient client, TimeSpan connectInterval, TimeSpan keepAlive) : base(client, "webSockets") { - this.connectInterval = connectInterval; - this.keepAlive = keepAlive; WebSocket = new ExchangeSharp.ClientWebSocket(); + this.connectInterval = connectInterval; + this.keepAlive = keepAlive; } ~WebsocketCustomTransport() @@ -326,9 +326,9 @@ protected override void Dispose(bool disposing) private void DisposeWebSocket() { WebSocket.Dispose(); - WebSocket = null; } + /* private void WebSocketOnClosed() { connection.Stop(); @@ -338,6 +338,7 @@ private void WebSocketOnError(Exception e) { connection.OnError(e); } + */ private Task WebSocketOnBinaryMessageReceived(IWebSocket socket, byte[] data) { @@ -366,7 +367,7 @@ private class HubListener private readonly SemaphoreSlim reconnectLock = new SemaphoreSlim(1); private WebsocketCustomTransport customTransport; - private HubConnection hubConnection; + private HubConnection? hubConnection; private IHubProxy hubProxy; private bool disposed; From 70b013ae93c6d0517703d11df93001a890945313 Mon Sep 17 00:00:00 2001 From: vslee Date: Tue, 29 Oct 2019 19:42:25 -0700 Subject: [PATCH 2/2] changed nullable to per file instead of projet wide - we can go back to project wide once we convert everything --- ExchangeSharp/API/Common/APIRequestMaker.cs | 4 ++-- ExchangeSharp/API/Common/BaseAPI.cs | 4 ++-- ExchangeSharp/API/Common/IAPIRequestMaker.cs | 6 +++--- ExchangeSharp/API/Common/IBaseAPI.cs | 4 ++-- .../API/Exchanges/Binance/ExchangeBinanceAPI.cs | 1 + .../API/Exchanges/Binance/Models/Currency.cs | 6 +++--- ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs | 8 ++++---- .../API/Exchanges/_Base/ExchangeAPIExtensions.cs | 14 +++++++------- ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs | 4 ++-- ExchangeSharp/ExchangeSharp.csproj | 1 - ExchangeSharp/Utility/CryptoUtility.cs | 6 +++--- ExchangeSharp/Utility/DataProtector.cs | 4 ++-- ExchangeSharp/Utility/SignalrManager.cs | 8 ++++---- 13 files changed, 35 insertions(+), 35 deletions(-) diff --git a/ExchangeSharp/API/Common/APIRequestMaker.cs b/ExchangeSharp/API/Common/APIRequestMaker.cs index b772543a..297ec641 100644 --- a/ExchangeSharp/API/Common/APIRequestMaker.cs +++ b/ExchangeSharp/API/Common/APIRequestMaker.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Generic; using System.IO; diff --git a/ExchangeSharp/API/Common/BaseAPI.cs b/ExchangeSharp/API/Common/BaseAPI.cs index 6855ad1c..b5ef8dba 100644 --- a/ExchangeSharp/API/Common/BaseAPI.cs +++ b/ExchangeSharp/API/Common/BaseAPI.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/ExchangeSharp/API/Common/IAPIRequestMaker.cs b/ExchangeSharp/API/Common/IAPIRequestMaker.cs index 67a77938..09415d94 100644 --- a/ExchangeSharp/API/Common/IAPIRequestMaker.cs +++ b/ExchangeSharp/API/Common/IAPIRequestMaker.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Generic; using System.Net; @@ -178,4 +178,4 @@ public interface IAPIRequestHandler /// RateGate RateLimit { get; } } -} \ No newline at end of file +} diff --git a/ExchangeSharp/API/Common/IBaseAPI.cs b/ExchangeSharp/API/Common/IBaseAPI.cs index 3a3a6dc7..97f51893 100644 --- a/ExchangeSharp/API/Common/IBaseAPI.cs +++ b/ExchangeSharp/API/Common/IBaseAPI.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Generic; using System.Globalization; diff --git a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs index 0ad7b99f..c357388e 100644 --- a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs +++ b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs @@ -9,6 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#nullable enable using System; using System.Collections.Generic; using System.Linq; diff --git a/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs b/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs index 0902faec..1c76955b 100644 --- a/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs +++ b/ExchangeSharp/API/Exchanges/Binance/Models/Currency.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable namespace ExchangeSharp.Binance { using Newtonsoft.Json; @@ -151,4 +151,4 @@ internal class Currency [JsonProperty("legalMoney")] public bool LegalMoney { get; set; } } -} \ No newline at end of file +} diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs index 735e790c..cbfdb0e1 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Generic; using System.IO; @@ -128,7 +128,7 @@ await GetHistoricalTradesAsync((e) => /// Clamped price protected async Task ClampOrderPrice(string marketSymbol, decimal outputPrice) { - ExchangeMarket market = await GetExchangeMarketFromCacheAsync(marketSymbol); + ExchangeMarket? market = await GetExchangeMarketFromCacheAsync(marketSymbol); return market == null ? outputPrice : CryptoUtility.ClampDecimal(market.MinPrice, market.MaxPrice, market.PriceStepSize, outputPrice); } @@ -140,7 +140,7 @@ protected async Task ClampOrderPrice(string marketSymbol, decimal outpu /// Clamped quantity protected async Task ClampOrderQuantity(string marketSymbol, decimal outputQuantity) { - ExchangeMarket market = await GetExchangeMarketFromCacheAsync(marketSymbol); + ExchangeMarket? market = await GetExchangeMarketFromCacheAsync(marketSymbol); return market == null ? outputQuantity : CryptoUtility.ClampDecimal(market.MinTradeSize, market.MaxTradeSize, market.QuantityStepSize, outputQuantity); } diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs index 7aaabf3a..5c74a48a 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -443,8 +443,8 @@ internal static ExchangeOrderBook ParseOrderBookFromJTokenDictionaries /// ExchangeTicker internal static async Task ParseTickerAsync(this ExchangeAPI api, JToken token, string marketSymbol, object askKey, object bidKey, object lastKey, object baseVolumeKey, - object quoteVolumeKey = null, object timestampKey = null, TimestampType timestampType = TimestampType.None, - object baseCurrencyKey = null, object quoteCurrencyKey = null, object idKey = null) + object? quoteVolumeKey = null, object? timestampKey = null, TimestampType timestampType = TimestampType.None, + object? baseCurrencyKey = null, object? quoteCurrencyKey = null, object? idKey = null) { if (token == null || !token.HasValues) { @@ -632,7 +632,7 @@ internal static T ParseTradeComponents(this JToken token, object amountKey, o /// Last volume value /// Receive base currency volume /// Receive quote currency volume - internal static void ParseVolumes(this JToken token, object baseVolumeKey, object quoteVolumeKey, decimal last, out decimal baseCurrencyVolume, out decimal quoteCurrencyVolume) + internal static void ParseVolumes(this JToken token, object baseVolumeKey, object? quoteVolumeKey, decimal last, out decimal baseCurrencyVolume, out decimal quoteCurrencyVolume) { // parse out volumes, handle cases where one or both do not exist if (baseVolumeKey == null) @@ -679,7 +679,7 @@ internal static void ParseVolumes(this JToken token, object baseVolumeKey, objec /// Weighted average key /// MarketCandle internal static MarketCandle ParseCandle(this INamed named, JToken token, string marketSymbol, int periodSeconds, object openKey, object highKey, object lowKey, - object closeKey, object timestampKey, TimestampType timestampType, object baseVolumeKey, object quoteVolumeKey = null, object weightedAverageKey = null) + object closeKey, object timestampKey, TimestampType timestampType, object baseVolumeKey, object? quoteVolumeKey = null, object? weightedAverageKey = null) { MarketCandle candle = new MarketCandle { @@ -703,4 +703,4 @@ internal static MarketCandle ParseCandle(this INamed named, JToken token, string return candle; } } -} \ No newline at end of file +} diff --git a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs index a2130b85..0dc7ebaf 100644 --- a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Collections.Generic; using System.Security; diff --git a/ExchangeSharp/ExchangeSharp.csproj b/ExchangeSharp/ExchangeSharp.csproj index 1e432482..2ca23093 100644 --- a/ExchangeSharp/ExchangeSharp.csproj +++ b/ExchangeSharp/ExchangeSharp.csproj @@ -6,7 +6,6 @@ false true latest - enable DigitalRuby.ExchangeSharp ExchangeSharp - C# API for cryptocurrency exchanges 0.6.2 diff --git a/ExchangeSharp/Utility/CryptoUtility.cs b/ExchangeSharp/Utility/CryptoUtility.cs index 9b57bedc..dc51cb84 100644 --- a/ExchangeSharp/Utility/CryptoUtility.cs +++ b/ExchangeSharp/Utility/CryptoUtility.cs @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -193,7 +193,7 @@ public static DateTime ToDateTimeInvariant(this object obj, DateTime defaultValu { return defaultValue; } - JValue jValue = obj as JValue; + JValue? jValue = obj as JValue; if (jValue != null && jValue.Value == null) { return defaultValue; @@ -215,7 +215,7 @@ public static T ConvertInvariant(this object obj, T defaultValue = default) { return defaultValue; } - JValue jValue = obj as JValue; + JValue? jValue = obj as JValue; if (jValue != null && jValue.Value == null) { return defaultValue; diff --git a/ExchangeSharp/Utility/DataProtector.cs b/ExchangeSharp/Utility/DataProtector.cs index 4c5ab053..3b18a62d 100644 --- a/ExchangeSharp/Utility/DataProtector.cs +++ b/ExchangeSharp/Utility/DataProtector.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable using System; using System.Globalization; using System.IO; diff --git a/ExchangeSharp/Utility/SignalrManager.cs b/ExchangeSharp/Utility/SignalrManager.cs index de824a19..0494f36e 100644 --- a/ExchangeSharp/Utility/SignalrManager.cs +++ b/ExchangeSharp/Utility/SignalrManager.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +#nullable enable #define HAS_SIGNALR #if HAS_SIGNALR @@ -366,9 +366,9 @@ private class HubListener private readonly List sockets = new List(); private readonly SemaphoreSlim reconnectLock = new SemaphoreSlim(1); - private WebsocketCustomTransport customTransport; + private WebsocketCustomTransport? customTransport; private HubConnection? hubConnection; - private IHubProxy hubProxy; + private IHubProxy? hubProxy; private bool disposed; private TimeSpan _connectInterval = TimeSpan.FromHours(1.0);