Merge branch 'master' into merge/release/3.0-preview7-to-master\n\nCommit migrated from 576150b007

This commit is contained in:
Doug Bunting 2019-07-14 11:42:06 -07:00 committed by GitHub
commit ddf3cb66f1
20 changed files with 376 additions and 58 deletions

View File

@ -147,7 +147,7 @@ module DotNet {
* @param identifier Identifies the globally-reachable function to be returned.
* @returns A Function instance.
*/
findJSFunction,
findJSFunction, // Note that this is used by the JS interop code inside Mono WebAssembly itself
/**
* Invokes the specified synchronous JavaScript function.
@ -227,8 +227,10 @@ module DotNet {
let result: any = window;
let resultIdentifier = 'window';
let lastSegmentValue: any;
identifier.split('.').forEach(segment => {
if (segment in result) {
lastSegmentValue = result;
result = result[segment];
resultIdentifier += '.' + segment;
} else {
@ -237,6 +239,8 @@ module DotNet {
});
if (result instanceof Function) {
result = result.bind(lastSegmentValue);
cachedJSFunctions[identifier] = result;
return result;
} else {
throw new Error(`The value '${resultIdentifier}' is not a function.`);

View File

@ -32,6 +32,7 @@ namespace Microsoft.JSInterop
}
public partial interface IJSRuntime
{
System.Threading.Tasks.Task<TValue> InvokeAsync<TValue>(string identifier, System.Collections.Generic.IEnumerable<object> args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
System.Threading.Tasks.Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
}
public partial class JSException : System.Exception
@ -59,8 +60,11 @@ namespace Microsoft.JSInterop
public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime
{
protected JSRuntimeBase() { }
protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson);
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
public System.Threading.Tasks.Task<T> InvokeAsync<T>(string identifier, System.Collections.Generic.IEnumerable<object> args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task<T> InvokeAsync<T>(string identifier, params object[] args) { throw null; }
protected virtual object OnDotNetInvocationException(System.Exception exception, string assemblyName, string methodIdentifier) { throw null; }
}
}
namespace Microsoft.JSInterop.Internal

View File

@ -75,19 +75,20 @@ namespace Microsoft.JSInterop
// code has to implement its own way of returning async results.
var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current;
var targetInstance = (object)null;
if (dotNetObjectId != default)
{
targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
}
// Using ExceptionDispatchInfo here throughout because we want to always preserve
// original stack traces.
object syncResult = null;
ExceptionDispatchInfo syncException = null;
object targetInstance = null;
try
{
if (dotNetObjectId != default)
{
targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
}
syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
}
catch (Exception ex)
@ -103,7 +104,7 @@ namespace Microsoft.JSInterop
else if (syncException != null)
{
// Threw synchronously, let's respond.
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException);
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier);
}
else if (syncResult is Task task)
{
@ -114,16 +115,17 @@ namespace Microsoft.JSInterop
if (t.Exception != null)
{
var exception = t.Exception.GetBaseException();
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception));
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier);
}
var result = TaskGenericsUtil.GetTaskResult(task);
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result);
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier);
}, TaskScheduler.Current);
}
else
{
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult);
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier);
}
}

View File

@ -1,7 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.JSInterop
@ -19,5 +20,15 @@ namespace Microsoft.JSInterop
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
/// <summary>
/// Invokes the specified JavaScript function asynchronously.
/// </summary>
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <param name="cancellationToken">A cancellation token to signal the cancellation of the operation.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
Task<TValue> InvokeAsync<TValue>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default);
}
}

View File

@ -3,6 +3,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text.Json;
using System.Threading;
@ -20,8 +22,71 @@ namespace Microsoft.JSInterop
private readonly ConcurrentDictionary<long, object> _pendingTasks
= new ConcurrentDictionary<long, object>();
private readonly ConcurrentDictionary<long, CancellationTokenRegistration> _cancellationRegistrations =
new ConcurrentDictionary<long, CancellationTokenRegistration>();
internal DotNetObjectRefManager ObjectRefManager { get; } = new DotNetObjectRefManager();
/// <summary>
/// Gets or sets the default timeout for asynchronous JavaScript calls.
/// </summary>
protected TimeSpan? DefaultAsyncTimeout { get; set; }
/// <summary>
/// Invokes the specified JavaScript function asynchronously.
/// </summary>
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <param name="cancellationToken">A cancellation token to signal the cancellation of the operation.</param>
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
public Task<T> InvokeAsync<T>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default)
{
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
var tcs = new TaskCompletionSource<T>(TaskContinuationOptions.RunContinuationsAsynchronously);
if (cancellationToken != default)
{
_cancellationRegistrations[taskId] = cancellationToken.Register(() =>
{
tcs.TrySetCanceled(cancellationToken);
CleanupTasksAndRegistrations(taskId);
});
}
_pendingTasks[taskId] = tcs;
try
{
if (cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled(cancellationToken);
CleanupTasksAndRegistrations(taskId);
return tcs.Task;
}
var argsJson = args?.Any() == true ?
JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) :
null;
BeginInvokeJS(taskId, identifier, argsJson);
return tcs.Task;
}
catch
{
CleanupTasksAndRegistrations(taskId);
throw;
}
}
private void CleanupTasksAndRegistrations(long taskId)
{
_pendingTasks.TryRemove(taskId, out _);
if (_cancellationRegistrations.TryRemove(taskId, out var registration))
{
registration.Dispose();
}
}
/// <summary>
/// Invokes the specified JavaScript function asynchronously.
/// </summary>
@ -31,49 +96,58 @@ namespace Microsoft.JSInterop
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
{
// We might consider also adding a default timeout here in case we don't want to
// risk a memory leak in the scenario where the JS-side code is failing to complete
// the operation.
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
var tcs = new TaskCompletionSource<T>();
_pendingTasks[taskId] = tcs;
try
if (!DefaultAsyncTimeout.HasValue)
{
var argsJson = args?.Length > 0 ?
JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) :
null;
BeginInvokeJS(taskId, identifier, argsJson);
return tcs.Task;
return InvokeAsync<T>(identifier, args, default);
}
catch
else
{
_pendingTasks.TryRemove(taskId, out _);
throw;
return InvokeWithDefaultCancellation<T>(identifier, args);
}
}
private async Task<T> InvokeWithDefaultCancellation<T>(string identifier, IEnumerable<object> args)
{
using (var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value))
{
// We need to await here due to the using
return await InvokeAsync<T>(identifier, args, cts.Token);
}
}
/// <summary>
/// Begins an asynchronous function invocation.
/// </summary>
/// <param name="asyncHandle">The identifier for the function invocation, or zero if no async callback is required.</param>
/// <param name="taskId">The identifier for the function invocation, or zero if no async callback is required.</param>
/// <param name="identifier">The identifier for the function to invoke.</param>
/// <param name="argsJson">A JSON representation of the arguments.</param>
protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson);
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
internal void EndInvokeDotNet(string callId, bool success, object resultOrException)
/// <summary>
/// Allows derived classes to configure the information about an exception in a JS interop call that gets sent to JavaScript.
/// </summary>
/// <remarks>
/// This callback can be used in remote JS interop scenarios to sanitize exceptions that happen on the server to avoid disclosing
/// sensitive information to remote browser clients.
/// </remarks>
/// <param name="exception">The exception that occurred.</param>
/// <param name="assemblyName">The assembly for the invoked .NET method.</param>
/// <param name="methodIdentifier">The identifier for the invoked .NET method.</param>
/// <returns>An object containing information about the exception.</returns>
protected virtual object OnDotNetInvocationException(Exception exception, string assemblyName, string methodIdentifier) => exception.ToString();
internal void EndInvokeDotNet(string callId, bool success, object resultOrException, string assemblyName, string methodIdentifier)
{
// For failures, the common case is to call EndInvokeDotNet with the Exception object.
// For these we'll serialize as something that's useful to receive on the JS side.
// If the value is not an Exception, we'll just rely on it being directly JSON-serializable.
if (!success && resultOrException is Exception)
if (!success && resultOrException is Exception ex)
{
resultOrException = resultOrException.ToString();
resultOrException = OnDotNetInvocationException(ex, assemblyName, methodIdentifier);
}
else if (!success && resultOrException is ExceptionDispatchInfo edi)
{
resultOrException = edi.SourceException.ToString();
resultOrException = OnDotNetInvocationException(edi.SourceException, assemblyName, methodIdentifier);
}
// We pass 0 as the async handle because we don't want the JS-side code to
@ -82,15 +156,19 @@ namespace Microsoft.JSInterop
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args);
}
internal void EndInvokeJS(long asyncHandle, bool succeeded, JSAsyncCallResult asyncCallResult)
internal void EndInvokeJS(long taskId, bool succeeded, JSAsyncCallResult asyncCallResult)
{
using (asyncCallResult?.JsonDocument)
{
if (!_pendingTasks.TryRemove(asyncHandle, out var tcs))
if (!_pendingTasks.TryRemove(taskId, out var tcs))
{
throw new ArgumentException($"There is no pending task with handle '{asyncHandle}'.");
// We should simply return if we can't find an id for the invocation.
// This likely means that the method that initiated the call defined a timeout and stopped waiting.
return;
}
CleanupTasksAndRegistrations(taskId);
if (succeeded)
{
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);

View File

@ -9,7 +9,9 @@ namespace Microsoft.JSInterop
{
public static readonly JsonSerializerOptions Options = new JsonSerializerOptions
{
MaxDepth = 32,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
}
}

View File

@ -381,6 +381,23 @@ namespace Microsoft.JSInterop.Tests
Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result[2].GetString());
});
[Fact]
public Task BeginInvoke_ThrowsWithInvalid_DotNetObjectRef() => WithJSRuntime(jsRuntime =>
{
// Arrange
var callId = "123";
var resultTask = jsRuntime.NextInvocationTask;
DotNetDispatcher.BeginInvoke(callId, null, "InvokableInstanceVoid", 1, null);
// Assert
using var jsonDocument = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson);
var result = jsonDocument.RootElement;
Assert.Equal(callId, result[0].GetString());
Assert.False(result[1].GetBoolean()); // Fails
Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result[2].GetString());
});
Task WithJSRuntime(Action<TestJSRuntime> testCode)
{
return WithJSRuntime(jsRuntime =>

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.JSInterop.Internal;
using Xunit;
@ -38,6 +39,69 @@ namespace Microsoft.JSInterop.Tests
});
}
[Fact]
public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout()
{
// Arrange
var runtime = new TestJSRuntime();
runtime.DefaultTimeout = TimeSpan.FromSeconds(1);
// Act
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
// Assert
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
}
[Fact]
public async Task InvokeAsync_CompletesSuccessfullyBeforeTimeout()
{
// Arrange
var runtime = new TestJSRuntime();
runtime.DefaultTimeout = TimeSpan.FromSeconds(10);
// Act
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
runtime.EndInvokeJS(2, succeeded: true, null);
// Assert
await task;
}
[Fact]
public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires()
{
// Arrange
using var cts = new CancellationTokenSource();
var runtime = new TestJSRuntime();
// Act
var task = runtime.InvokeAsync<object>("test identifier 1", new object[] { "arg1", 123, true }, cts.Token);
cts.Cancel();
// Assert
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
}
[Fact]
public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested()
{
// Arrange
using var cts = new CancellationTokenSource();
cts.Cancel();
var runtime = new TestJSRuntime();
// Act
var task = runtime.InvokeAsync<object>("test identifier 1", new object[] { "arg1", 123, true }, cts.Token);
cts.Cancel();
// Assert
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
Assert.Empty(runtime.BeginInvokeCalls);
}
[Fact]
public void CanCompleteAsyncCallsAsSuccess()
{
@ -115,21 +179,19 @@ namespace Microsoft.JSInterop.Tests
}
[Fact]
public void CannotCompleteSameAsyncCallMoreThanOnce()
public async Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync()
{
// Arrange
var runtime = new TestJSRuntime();
// Act/Assert
runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle;
runtime.OnEndInvoke(asyncHandle, true, null);
var ex = Assert.Throws<ArgumentException>(() =>
{
// Second "end invoke" will fail
runtime.OnEndInvoke(asyncHandle, true, null);
});
Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message);
runtime.OnEndInvoke(asyncHandle, true, new JSAsyncCallResult(JsonDocument.Parse("{}"), JsonDocument.Parse("{\"Message\": \"Some data\"}").RootElement.GetProperty("Message")));
runtime.OnEndInvoke(asyncHandle, false, new JSAsyncCallResult(null, JsonDocument.Parse("{\"Message\": \"Exception\"}").RootElement.GetProperty("Message")));
var result = await task;
Assert.Equal("Some data", result);
}
[Fact]
@ -168,10 +230,50 @@ namespace Microsoft.JSInterop.Tests
Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4));
}
[Fact]
public void CanSanitizeDotNetInteropExceptions()
{
// Arrange
var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " +
"display more detailed information about the error that occurred.";
string GetMessage(string assembly, string method) => $"An error ocurred while invoking '[{assembly}]::{method}'. Swapping to 'Development' environment will " +
"display more detailed information about the error that occurred.";
var runtime = new TestJSRuntime()
{
OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) }
};
var exception = new Exception("Some really sensitive data in here");
// Act
runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method");
// Assert
var call = runtime.BeginInvokeCalls.Single();
Assert.Equal(0, call.AsyncHandle);
Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", call.Identifier);
Assert.Equal($"[\"0\",false,{{\"message\":\"{expectedMessage.Replace("'", "\\u0027")}\"}}]", call.ArgsJson);
}
private class JSError
{
public string Message { get; set; }
}
class TestJSRuntime : JSRuntimeBase
{
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
public TimeSpan? DefaultTimeout
{
set
{
base.DefaultAsyncTimeout = value;
}
}
public class BeginInvokeAsyncArgs
{
public long AsyncHandle { get; set; }
@ -179,6 +281,18 @@ namespace Microsoft.JSInterop.Tests
public string ArgsJson { get; set; }
}
public Func<Exception, string, string, object> OnDotNetException { get; set; }
protected override object OnDotNetInvocationException(Exception exception, string assemblyName, string methodName)
{
if (OnDotNetException != null)
{
return OnDotNetException(exception, assemblyName, methodName);
}
return base.OnDotNetInvocationException(exception, assemblyName, methodName);
}
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
{
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
@ -29,6 +31,9 @@ namespace Microsoft.JSInterop.Tests
{
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
=> throw new NotImplementedException();
public Task<TValue> InvokeAsync<TValue>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
}
}
}

View File

@ -1,5 +1,5 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -56,6 +56,10 @@ namespace Microsoft.Extensions.Localization
SearchedLocation = searchedLocation;
}
/// <summary>
/// Implicitly converts the <see cref="LocalizedString"/> to a <see cref="string"/>.
/// </summary>
/// <param name="localizedString">The string to be implicitly converted.</param>
public static implicit operator string(LocalizedString localizedString)
{
return localizedString?.Value;
@ -87,4 +91,4 @@ namespace Microsoft.Extensions.Localization
/// <returns>The actual string.</returns>
public override string ToString() => Value;
}
}
}

View File

@ -1,11 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// Extension methods for operating on <see cref="IStringLocalizer" /> instances.
/// </summary>
public static class StringLocalizerExtensions
{
/// <summary>

View File

@ -8,6 +8,12 @@ namespace Microsoft.Extensions.Localization
/// </summary>
public class LocalizationOptions
{
/// <summary>
/// Creates a new <see cref="LocalizationOptions" />.
/// </summary>
public LocalizationOptions()
{ }
/// <summary>
/// The relative path under application root where resource files are located.
/// </summary>

View File

@ -177,7 +177,7 @@ namespace Microsoft.Extensions.Localization
/// <summary>
/// Returns all strings in the specified culture.
/// </summary>
/// <param name="includeParentCultures"></param>
/// <param name="includeParentCultures">Whether to include parent cultures in the search for a resource.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to get strings for.</param>
/// <returns>The strings.</returns>
protected IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, CultureInfo culture)

View File

@ -1,5 +1,5 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
@ -14,6 +14,13 @@ namespace Microsoft.Extensions.Localization
{
private readonly ConcurrentDictionary<string, IList<string>> _cache = new ConcurrentDictionary<string, IList<string>>();
/// <summary>
/// Creates a new <see cref="ResourceNamesCache" />
/// </summary>
public ResourceNamesCache()
{
}
/// <inheritdoc />
public IList<string> GetOrAdd(string name, Func<string, IList<string>> valueFactory)
{

View File

@ -8,6 +8,11 @@ using System.Threading;
namespace Microsoft.Extensions.ObjectPool
{
/// <summary>
/// Default implementation of <see cref="ObjectPool{T}"/>.
/// </summary>
/// <typeparam name="T">The type to pool objects for.</typeparam>
/// <remarks>This implementation keeps a cache of retained objects. This means that if objects are returned when the pool has already reached "maximumRetained" objects they will be available to be Garbage Collected.</remarks>
public class DefaultObjectPool<T> : ObjectPool<T> where T : class
{
private protected readonly ObjectWrapper[] _items;
@ -18,11 +23,20 @@ namespace Microsoft.Extensions.ObjectPool
// This class was introduced in 2.1 to avoid the interface call where possible
private protected readonly PooledObjectPolicy<T> _fastPolicy;
/// <summary>
/// Creates an instance of <see cref="DefaultObjectPool{T}"/>.
/// </summary>
/// <param name="policy">The pooling policy to use.</param>
public DefaultObjectPool(IPooledObjectPolicy<T> policy)
: this(policy, Environment.ProcessorCount * 2)
{
}
/// <summary>
/// Creates an instance of <see cref="DefaultObjectPool{T}"/>.
/// </summary>
/// <param name="policy">The pooling policy to use.</param>
/// <param name="maximumRetained">The maximum number of objects to retain in the pool.</param>
public DefaultObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained)
{
_policy = policy ?? throw new ArgumentNullException(nameof(policy));

View File

@ -5,10 +5,17 @@ using System;
namespace Microsoft.Extensions.ObjectPool
{
/// <summary>
/// The default <see cref="ObjectPoolProvider"/>.
/// </summary>
public class DefaultObjectPoolProvider : ObjectPoolProvider
{
/// <summary>
/// The maximum number of objects to retain in the pool.
/// </summary>
public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2;
/// <inheritdoc/>
public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy)
{
if (policy == null)

View File

@ -3,10 +3,23 @@
namespace Microsoft.Extensions.ObjectPool
{
/// <summary>
/// Represents a policy for managing pooled objects.
/// </summary>
/// <typeparam name="T">The type of object which is being pooled.</typeparam>
public interface IPooledObjectPolicy<T>
{
/// <summary>
/// Create a <typeparamref name="T"/>.
/// </summary>
/// <returns>The <typeparamref name="T"/> which was created.</returns>
T Create();
/// <summary>
/// Runs some processing when an object was returned to the pool. Can be used to reset the state of an object and indicate if the object should be returned to the pool.
/// </summary>
/// <param name="obj">The object to return to the pool.</param>
/// <returns><code>true</code> if the object should be returned to the pool. <code>false</code> if it's not possible/desirable for the pool to keep the object.</returns>
bool Return(T obj);
}
}

View File

@ -3,15 +3,31 @@
namespace Microsoft.Extensions.ObjectPool
{
/// <summary>
/// A pool of objects.
/// </summary>
/// <typeparam name="T">The type of objects to pool.</typeparam>
public abstract class ObjectPool<T> where T : class
{
/// <summary>
/// Gets an object from the pool if one is available, otherwise creates one.
/// </summary>
/// <returns>A <typeparamref name="T"/>.</returns>
public abstract T Get();
/// <summary>
/// Return an object to the pool.
/// </summary>
/// <param name="obj">The object to add to the pool.</param>
public abstract void Return(T obj);
}
/// <summary>
/// Methods for creating <see cref="ObjectPool{T}"/> instances.
/// </summary>
public static class ObjectPool
{
/// <inheritdoc />
public static ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy = null) where T : class, new()
{
var provider = new DefaultObjectPoolProvider();

View File

@ -3,13 +3,24 @@
namespace Microsoft.Extensions.ObjectPool
{
/// <summary>
/// A provider of <see cref="ObjectPool{T}"/> instances.
/// </summary>
public abstract class ObjectPoolProvider
{
/// <summary>
/// Creates an <see cref="ObjectPool"/>.
/// </summary>
/// <typeparam name="T">The type to create a pool for.</typeparam>
public ObjectPool<T> Create<T>() where T : class, new()
{
return Create<T>(new DefaultPooledObjectPolicy<T>());
}
/// <summary>
/// Creates an <see cref="ObjectPool"/> with the given <see cref="IPooledObjectPolicy{T}"/>.
/// </summary>
/// <typeparam name="T">The type to create a pool for.</typeparam>
public abstract ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T : class;
}
}

View File

@ -19,8 +19,8 @@ namespace Microsoft.Extensions.CommandLineUtils
/// <remarks>
/// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
/// </remarks>
/// <param name="args"></param>
/// <returns></returns>
/// <param name="args">The arguments to concatenate.</param>
/// <returns>The escaped arguments, concatenated.</returns>
public static string EscapeAndConcatenate(IEnumerable<string> args)
=> string.Join(" ", args.Select(EscapeSingleArg));
@ -104,6 +104,6 @@ namespace Microsoft.Extensions.CommandLineUtils
}
private static bool ContainsWhitespace(string argument)
=> argument.IndexOfAny(new [] { ' ', '\t', '\n' }) >= 0;
=> argument.IndexOfAny(new[] { ' ', '\t', '\n' }) >= 0;
}
}