Merge pull request #521 from MihaZupan/random-perf-2

Random perf improvements
This commit is contained in:
Alexandre Mutel
2021-03-10 07:27:10 +01:00
committed by GitHub
6 changed files with 336 additions and 249 deletions

View File

@@ -11,11 +11,14 @@ namespace Markdig.Tests
[Test]
public void TestToHtml()
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/");
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/");
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
}
[Test]
@@ -24,49 +27,66 @@ namespace Markdig.Tests
var pipeline = new MarkdownPipelineBuilder()
.Build();
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
}
[Test]
public void TestToHtmlWithWriter()
{
StringWriter writer = new StringWriter();
var writer = new StringWriter();
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
writer = new StringWriter();
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
for (int i = 0; i < 5; i++)
{
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestConvert()
{
StringWriter writer = new StringWriter();
HtmlRenderer renderer = new HtmlRenderer(writer);
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
writer = new StringWriter();
renderer = new HtmlRenderer(writer);
@@ -74,9 +94,13 @@ namespace Markdig.Tests
.UseAdvancedExtensions()
.Build();
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
for (int i = 0; i < 5; i++)
{
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
@@ -88,62 +112,77 @@ namespace Markdig.Tests
.UsePreciseSourceLocation()
.Build();
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
Assert.AreEqual(1, document.LineCount);
Assert.AreEqual(markdown.Length, document.Span.Length);
Assert.AreEqual(1, document.LineStartIndexes.Count);
Assert.AreEqual(0, document.LineStartIndexes[0]);
Assert.AreEqual(1, document.LineCount);
Assert.AreEqual(markdown.Length, document.Span.Length);
Assert.AreEqual(1, document.LineStartIndexes.Count);
Assert.AreEqual(0, document.LineStartIndexes[0]);
Assert.AreEqual(1, document.Count);
ParagraphBlock paragraph = document[0] as ParagraphBlock;
Assert.NotNull(paragraph);
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
Assert.NotNull(literal);
Assert.AreEqual("This is a text with some ", literal.ToString());
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
Assert.NotNull(emphasis);
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
Assert.NotNull(emphasisLiteral);
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
Assert.Null(emphasisLiteral.NextSibling);
Assert.Null(emphasis.NextSibling);
Assert.AreEqual(1, document.Count);
ParagraphBlock paragraph = document[0] as ParagraphBlock;
Assert.NotNull(paragraph);
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
Assert.NotNull(literal);
Assert.AreEqual("This is a text with some ", literal.ToString());
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
Assert.NotNull(emphasis);
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
Assert.NotNull(emphasisLiteral);
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
Assert.Null(emphasisLiteral.NextSibling);
Assert.Null(emphasis.NextSibling);
}
}
[Test]
public void TestNormalize()
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
for (int i = 0; i < 5; i++)
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestNormalizeWithWriter()
{
StringWriter writer = new StringWriter();
for (int i = 0; i < 5; i++)
{
var writer = new StringWriter();
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestToPlainText()
{
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
Assert.AreEqual("Hello, world!\n", plainText);
for (int i = 0; i < 5; i++)
{
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
Assert.AreEqual("Hello, world!\n", plainText);
}
}
[Test]
public void TestToPlainTextWithWriter()
{
StringWriter writer = new StringWriter();
for (int i = 0; i < 5; i++)
{
var writer = new StringWriter();
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
string plainText = writer.ToString();
Assert.AreEqual("Hello, world!\n", plainText);
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
string plainText = writer.ToString();
Assert.AreEqual("Hello, world!\n", plainText);
}
}
}
}

View File

@@ -23,6 +23,25 @@ namespace Markdig
{
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
private static readonly MarkdownPipeline _defaultPipeline = new MarkdownPipelineBuilder().Build();
private static readonly MarkdownPipeline _defaultTrackTriviaPipeline = new MarkdownPipelineBuilder().EnableTrackTrivia().Build();
private static MarkdownPipeline GetPipeline(MarkdownPipeline? pipeline, string markdown)
{
if (pipeline is null)
{
return _defaultPipeline;
}
var selfPipeline = pipeline.Extensions.Find<SelfPipelineExtension>();
if (selfPipeline is not null)
{
return selfPipeline.CreatePipelineFromInput(markdown);
}
return pipeline;
}
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.
/// </summary>
@@ -49,14 +68,15 @@ namespace Markdig
/// <returns>A normalized markdown text.</returns>
public static MarkdownDocument Normalize(string markdown, TextWriter writer, NormalizeOptions? options = null, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
{
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
pipeline = GetPipeline(pipeline, markdown);
var document = MarkdownParser.Parse(markdown, pipeline, context);
// We override the renderer with our own writer
var renderer = new NormalizeRenderer(writer, options);
pipeline.Setup(renderer);
var document = Parse(markdown, pipeline, context);
renderer.Render(document);
writer.Flush();
@@ -74,18 +94,12 @@ namespace Markdig
public static string ToHtml(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
{
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
var renderer = pipeline.GetCacheableHtmlRenderer();
pipeline = GetPipeline(pipeline, markdown);
var document = Parse(markdown, pipeline, context);
renderer.Render(document);
renderer.Writer.Flush();
var document = MarkdownParser.Parse(markdown, pipeline, context);
string html = renderer.Writer.ToString()!;
pipeline.ReleaseCacheableHtmlRenderer(renderer);
return html;
return ToHtml(document, pipeline);
}
/// <summary>
@@ -95,19 +109,19 @@ namespace Markdig
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns>
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
public static string ToHtml(this MarkdownDocument document, MarkdownPipeline pipeline = null)
public static string ToHtml(this MarkdownDocument document, MarkdownPipeline? pipeline = null)
{
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
pipeline ??= new MarkdownPipelineBuilder().Build();
if (document is null) ThrowHelper.ArgumentNullException(nameof(document));
var renderer = pipeline.GetCacheableHtmlRenderer();
pipeline ??= _defaultPipeline;
using var rentedRenderer = pipeline.RentHtmlRenderer();
HtmlRenderer renderer = rentedRenderer.Instance;
renderer.Render(document);
renderer.Writer.Flush();
string html = renderer.Writer.ToString();
pipeline.ReleaseCacheableHtmlRenderer(renderer);
return html;
return renderer.Writer.ToString() ?? string.Empty;
}
/// <summary>
@@ -123,14 +137,14 @@ namespace Markdig
{
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
if (writer is null) ThrowHelper.ArgumentNullException_writer();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer);
pipeline = GetPipeline(pipeline, markdown);
var document = MarkdownParser.Parse(markdown, pipeline, context);
using var rentedRenderer = pipeline.RentHtmlRenderer(writer);
HtmlRenderer renderer = rentedRenderer.Instance;
var document = Parse(markdown, pipeline, context);
renderer.Render(document);
writer.Flush();
@@ -149,10 +163,11 @@ namespace Markdig
{
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
if (renderer is null) ThrowHelper.ArgumentNullException(nameof(renderer));
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
var document = Parse(markdown, pipeline, context);
pipeline = GetPipeline(pipeline, markdown);
var document = MarkdownParser.Parse(markdown, pipeline, context);
pipeline.Setup(renderer);
return renderer.Render(document);
}
@@ -168,9 +183,7 @@ namespace Markdig
{
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
MarkdownPipeline? pipeline = trackTrivia ? new MarkdownPipelineBuilder()
.EnableTrackTrivia()
.Build() : null;
MarkdownPipeline? pipeline = trackTrivia ? _defaultTrackTriviaPipeline : null;
return Parse(markdown, pipeline);
}
@@ -186,22 +199,12 @@ namespace Markdig
public static MarkdownDocument Parse(string markdown, MarkdownPipeline? pipeline, MarkdownParserContext? context = null)
{
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
pipeline = GetPipeline(pipeline, markdown);
return MarkdownParser.Parse(markdown, pipeline, context);
}
private static MarkdownPipeline CheckForSelfPipeline(MarkdownPipeline pipeline, string markdown)
{
var selfPipeline = pipeline.Extensions.Find<SelfPipelineExtension>();
if (selfPipeline != null)
{
return selfPipeline.CreatePipelineFromInput(markdown);
}
return pipeline;
}
/// <summary>
/// Converts a Markdown string to Plain text and output to the specified writer.
/// </summary>
@@ -213,10 +216,12 @@ namespace Markdig
/// <exception cref="ArgumentNullException">if reader or writer variable are null</exception>
public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
{
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
if (writer == null) ThrowHelper.ArgumentNullException_writer();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
if (writer is null) ThrowHelper.ArgumentNullException_writer();
pipeline = GetPipeline(pipeline, markdown);
var document = MarkdownParser.Parse(markdown, pipeline, context);
// We override the renderer with our own writer
var renderer = new HtmlRenderer(writer)
@@ -227,7 +232,6 @@ namespace Markdig
};
pipeline.Setup(renderer);
var document = Parse(markdown, pipeline, context);
renderer.Render(document);
writer.Flush();
@@ -244,7 +248,7 @@ namespace Markdig
/// <exception cref="ArgumentNullException">if markdown variable is null</exception>
public static string ToPlainText(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null)
{
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
if (markdown is null) ThrowHelper.ArgumentNullException_markdown();
var writer = new StringWriter();
ToPlainText(markdown, writer, pipeline, context);
return writer.ToString();

View File

@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Text;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers;
@@ -68,40 +69,76 @@ namespace Markdig
}
private HtmlRendererCache _rendererCache = null;
private HtmlRendererCache _rendererCache, _rendererCacheForCustomWriter;
internal HtmlRenderer GetCacheableHtmlRenderer()
internal RentedHtmlRenderer RentHtmlRenderer(TextWriter writer = null)
{
if (_rendererCache is null)
HtmlRendererCache cache = writer is null
? _rendererCache ??= new HtmlRendererCache(this, customWriter: false)
: _rendererCacheForCustomWriter ??= new HtmlRendererCache(this, customWriter: true);
HtmlRenderer renderer = cache.Get();
if (writer is not null)
{
_rendererCache = new HtmlRendererCache
{
OnNewInstanceCreated = Setup
};
renderer.Writer = writer;
}
return _rendererCache.Get();
}
internal void ReleaseCacheableHtmlRenderer(HtmlRenderer renderer)
{
_rendererCache.Release(renderer);
return new RentedHtmlRenderer(cache, renderer);
}
private sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
internal sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
{
public Action<HtmlRenderer> OnNewInstanceCreated;
private const int InitialCapacity = 1024;
private static readonly StringWriter _dummyWriter = new();
private readonly MarkdownPipeline _pipeline;
private readonly bool _customWriter;
public HtmlRendererCache(MarkdownPipeline pipeline, bool customWriter = false)
{
_pipeline = pipeline;
_customWriter = customWriter;
}
protected override HtmlRenderer NewInstance()
{
var writer = new StringWriter();
var writer = _customWriter ? _dummyWriter : new StringWriter(new StringBuilder(InitialCapacity));
var renderer = new HtmlRenderer(writer);
OnNewInstanceCreated(renderer);
_pipeline.Setup(renderer);
return renderer;
}
protected override void Reset(HtmlRenderer instance)
{
instance.Reset();
instance.ResetInternal();
if (_customWriter)
{
instance.Writer = _dummyWriter;
}
else
{
((StringWriter)instance.Writer).GetStringBuilder().Length = 0;
}
}
}
internal readonly struct RentedHtmlRenderer : IDisposable
{
private readonly HtmlRendererCache _cache;
public readonly HtmlRenderer Instance;
internal RentedHtmlRenderer(HtmlRendererCache cache, HtmlRenderer renderer)
{
_cache = cache;
Instance = renderer;
}
public void Dispose() => _cache.Release(Instance);
}
}
}

View File

@@ -155,28 +155,19 @@ namespace Markdig.Parsers
return text.Replace('\0', CharHelper.ReplacementChar);
}
private sealed class ContainerItemCache : DefaultObjectCache<ContainerItem>
{
protected override void Reset(ContainerItem instance)
{
instance.Container = null;
instance.Index = 0;
}
}
private void ProcessInlines()
{
// "stackless" processor
var cache = new ContainerItemCache();
var blocks = new Stack<ContainerItem>();
int blockCount = 1;
var blocks = new ContainerItem[4];
// TODO: Use an ObjectCache for ContainerItem
blocks.Push(new ContainerItem(document));
blocks[0] = new ContainerItem(document);
document.OnProcessInlinesBegin(inlineProcessor);
while (blocks.Count > 0)
while (blockCount != 0)
{
process_new_block:
var item = blocks.Peek();
ref ContainerItem item = ref blocks[blockCount - 1];
var container = item.Container;
for (; item.Index < container.Count; item.Index++)
@@ -212,35 +203,31 @@ namespace Markdig.Parsers
// Else we have processed it
item.Index++;
}
var newItem = cache.Get();
newItem.Container = (ContainerBlock)block;
block.OnProcessInlinesBegin(inlineProcessor);
newItem.Index = 0;
ThrowHelper.CheckDepthLimit(blocks.Count);
blocks.Push(newItem);
if (blockCount == blocks.Length)
{
Array.Resize(ref blocks, blockCount * 2);
ThrowHelper.CheckDepthLimit(blocks.Length);
}
blocks[blockCount++] = new ContainerItem(newContainer);
newContainer.OnProcessInlinesBegin(inlineProcessor);
goto process_new_block;
}
}
item = blocks.Pop();
container = item.Container;
container.OnProcessInlinesEnd(inlineProcessor);
cache.Release(item);
blocks[--blockCount] = default;
}
}
private class ContainerItem
private struct ContainerItem
{
public ContainerItem()
{
}
public ContainerItem(ContainerBlock container)
{
Container = container;
Index = 0;
}
public ContainerBlock Container;
public readonly ContainerBlock Container;
public int Index;
}

View File

@@ -28,9 +28,7 @@ namespace Markdig.Renderers
protected TextRendererBase(TextWriter writer)
{
if (writer == null) ThrowHelper.ArgumentNullException_writer();
this.Writer = writer;
// By default we output a newline with '\n' only even on Windows platforms
Writer.NewLine = "\n";
Writer = writer;
}
/// <summary>
@@ -47,6 +45,8 @@ namespace Markdig.Renderers
ThrowHelper.ArgumentNullException(nameof(value));
}
// By default we output a newline with '\n' only even on Windows platforms
value.NewLine = "\n";
writer = value;
}
}
@@ -128,6 +128,11 @@ namespace Markdig.Renderers
ThrowHelper.InvalidOperationException("Cannot reset this TextWriter instance");
}
ResetInternal();
}
internal void ResetInternal()
{
childrenDepth = 0;
previousWasLine = true;
indents.Clear();

View File

@@ -22,8 +22,7 @@ namespace Markdig.Syntax
/// as we expect less than 5~10 entries, usually typically 1 (HtmlAttributes)
/// so it will gives faster access than a Dictionary, and lower memory occupation
/// </summary>
private DataEntry[] attachedDatas;
private int count;
private DataEntries _attachedDatas;
/// <summary>
/// Gets or sets the text column this instance was declared (zero-based).
@@ -55,33 +54,7 @@ namespace Markdig.Syntax
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <exception cref="ArgumentNullException">if key is null</exception>
public void SetData(object key, object value)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
attachedDatas = new DataEntry[1];
}
else
{
for (int i = 0; i < count; i++)
{
if (attachedDatas[i].Key == key)
{
attachedDatas[i].Value = value;
return;
}
}
if (count == attachedDatas.Length)
{
var temp = new DataEntry[attachedDatas.Length + 1];
Array.Copy(attachedDatas, 0, temp, 0, count);
attachedDatas = temp;
}
}
attachedDatas[count] = new DataEntry(key, value);
count++;
}
public void SetData(object key, object value) => (_attachedDatas ??= new DataEntries()).SetData(key, value);
/// <summary>
/// Determines whether this instance contains the specified key data.
@@ -89,23 +62,7 @@ namespace Markdig.Syntax
/// <param name="key">The key.</param>
/// <returns><c>true</c> if a data with the key is stored</returns>
/// <exception cref="ArgumentNullException">if key is null</exception>
public bool ContainsData(object key)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
return false;
}
for (int i = 0; i < count; i++)
{
if (attachedDatas[i].Key == key)
{
return true;
}
}
return false;
}
public bool ContainsData(object key) => _attachedDatas?.ContainsData(key) ?? false;
/// <summary>
/// Gets the associated data for the specified key.
@@ -113,22 +70,7 @@ namespace Markdig.Syntax
/// <param name="key">The key.</param>
/// <returns>The associated data or null if none</returns>
/// <exception cref="ArgumentNullException">if key is null</exception>
public object GetData(object key)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
return null;
}
for (int i = 0; i < count; i++)
{
if (attachedDatas[i].Key == key)
{
return attachedDatas[i].Value;
}
}
return null;
}
public object GetData(object key) => _attachedDatas?.GetData(key);
/// <summary>
/// Removes the associated data for the specified key.
@@ -136,44 +78,117 @@ namespace Markdig.Syntax
/// <param name="key">The key.</param>
/// <returns><c>true</c> if the data was removed; <c>false</c> otherwise</returns>
/// <exception cref="ArgumentNullException"></exception>
public bool RemoveData(object key)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
return true;
}
public bool RemoveData(object key) => _attachedDatas?.RemoveData(key) ?? false;
for (int i = 0; i < count; i++)
private class DataEntries
{
private struct DataEntry
{
if (attachedDatas[i].Key == key)
public readonly object Key;
public object Value;
public DataEntry(object key, object value)
{
if (i < count - 1)
{
Array.Copy(attachedDatas, i + 1, attachedDatas, i, count - i - 1);
}
count--;
attachedDatas[count] = new DataEntry();
return true;
Key = key;
Value = value;
}
}
return false;
}
/// <summary>
/// Store a Key/Value pair.
/// </summary>
private struct DataEntry
{
public DataEntry(object key, object value)
private DataEntry[] _entries;
private int _count;
public DataEntries()
{
Key = key;
Value = value;
_entries = new DataEntry[2];
}
public readonly object Key;
public void SetData(object key, object value)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
public object Value;
DataEntry[] entries = _entries;
int count = _count;
for (int i = 0; i < entries.Length && i < count; i++)
{
ref DataEntry entry = ref entries[i];
if (entry.Key == key)
{
entry.Value = value;
return;
}
}
if (count == entries.Length)
{
Array.Resize(ref _entries, count + 2);
}
_entries[count] = new DataEntry(key, value);
_count++;
}
public object GetData(object key)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
DataEntry[] entries = _entries;
int count = _count;
for (int i = 0; i < entries.Length && i < count; i++)
{
ref DataEntry entry = ref entries[i];
if (entry.Key == key)
{
return entry.Value;
}
}
return null;
}
public bool ContainsData(object key)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
DataEntry[] entries = _entries;
int count = _count;
for (int i = 0; i < entries.Length && i < count; i++)
{
if (entries[i].Key == key)
{
return true;
}
}
return false;
}
public bool RemoveData(object key)
{
if (key == null) ThrowHelper.ArgumentNullException_key();
DataEntry[] entries = _entries;
int count = _count;
for (int i = 0; i < entries.Length && i < count; i++)
{
if (entries[i].Key == key)
{
if (i < count - 1)
{
Array.Copy(entries, i + 1, entries, i, count - i - 1);
}
count--;
entries[count] = default;
_count = count;
return true;
}
}
return false;
}
}
}
}