diff --git a/.gitignore b/.gitignore index 445484b62..bbeab14e4 100644 --- a/.gitignore +++ b/.gitignore @@ -197,3 +197,21 @@ FakesAssemblies/ *.opt *Solved.cs + +!TagsCloudApp_Tests/[Bb]in/ +TagsCloudApp_Tests/[Bb]in/* +!TagsCloudApp_Tests/[Bb]in/[Dd]ebug/ +TagsCloudApp_Tests/[Bb]in/[Dd]ebug/* +!TagsCloudApp_Tests/[Bb]in/[Dd]ebug/net8.0-windows/ +TagsCloudApp_Tests/[Bb]in/[Dd]ebug/net8.0-windows/* +!TagsCloudApp_Tests/[Bb]in/[Dd]ebug/net8.0-windows/expected/ + + +!TagsCloudConsoleInterface/[Bb]in/ +TagsCloudConsoleInterface/[Bb]in/* +!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/ +TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/* +!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/ +TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/* +!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/in.txt +!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/exclude.txt diff --git a/RectanglesCloudPositioning/Configs/RectanglesPositioningConfig.cs b/RectanglesCloudPositioning/Configs/RectanglesPositioningConfig.cs new file mode 100644 index 000000000..09803081a --- /dev/null +++ b/RectanglesCloudPositioning/Configs/RectanglesPositioningConfig.cs @@ -0,0 +1,5 @@ +using System.Drawing; + +namespace RectanglesCloudPositioning.Configs; + +public record RectanglesPositioningConfig(Point Center, int RaysCount, Func RadiusEquation); diff --git a/RectanglesCloudPositioning/Direction.cs b/RectanglesCloudPositioning/Direction.cs new file mode 100644 index 000000000..3952f9d02 --- /dev/null +++ b/RectanglesCloudPositioning/Direction.cs @@ -0,0 +1,14 @@ +namespace RectanglesCloudPositioning; + +internal enum Direction +{ + None = 0, + + Left = 1, + + Right = 2, + + Up = 3, + + Down = 4, +} diff --git a/RectanglesCloudPositioning/ICloudLayouter.cs b/RectanglesCloudPositioning/ICloudLayouter.cs new file mode 100644 index 000000000..4016732c2 --- /dev/null +++ b/RectanglesCloudPositioning/ICloudLayouter.cs @@ -0,0 +1,21 @@ +using System.Drawing; +using FluentResults; + +namespace RectanglesCloudPositioning; + +public interface ICloudLayouter +{ + public Result PutNextRectangle(Size rectangleSize) + { + return Result + .FailIf( + rectangleSize.Width <= 0 || rectangleSize.Height <= 0, + new Error("Width and height must be greater than zero.")) + .Bind(() => Result + .Try( + () => PutNextRectangleOrThrow(rectangleSize), + ex => new Error($"Cannot put a rectangle of size {rectangleSize}. {ex.Message}"))); + } + + protected Rectangle PutNextRectangleOrThrow(Size rectangleSize); +} diff --git a/RectanglesCloudPositioning/NoEqualityComparer.cs b/RectanglesCloudPositioning/NoEqualityComparer.cs new file mode 100644 index 000000000..035d128df --- /dev/null +++ b/RectanglesCloudPositioning/NoEqualityComparer.cs @@ -0,0 +1,18 @@ +namespace RectanglesCloudPositioning; + +internal class NoEqualityComparer : IComparer + where T : IComparable +{ + public int Compare(T? x, T? y) + { + if (x == null) + { + return -1; + } + + var comparison = x.CompareTo(y); + return comparison == 0 + ? -1 + : comparison; + } +} diff --git a/RectanglesCloudPositioning/RectanglesCloudPositioning.csproj b/RectanglesCloudPositioning/RectanglesCloudPositioning.csproj new file mode 100644 index 000000000..a1897193e --- /dev/null +++ b/RectanglesCloudPositioning/RectanglesCloudPositioning.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + + + + True + + + + True + + + + + + + diff --git a/RectanglesCloudPositioning/ShapedCloudLayouter.cs b/RectanglesCloudPositioning/ShapedCloudLayouter.cs new file mode 100644 index 000000000..5e6265d08 --- /dev/null +++ b/RectanglesCloudPositioning/ShapedCloudLayouter.cs @@ -0,0 +1,88 @@ +using FluentResults; +using RectanglesCloudPositioning.Configs; +using System.Drawing; + +namespace RectanglesCloudPositioning; + +public class ShapedCloudLayouter : ICloudLayouter +{ + private readonly List rectangles = []; + + private readonly Func radiusEquation; + private readonly Point center; + private readonly int raysCount; + + private IEnumerator pointsEnumerator; + private int radius; + + public ShapedCloudLayouter(RectanglesPositioningConfig config) + : this(config.Center, config.RaysCount, config.RadiusEquation) + { + } + + public ShapedCloudLayouter(Point center, int raysCount, Func radiusEquation) + { + ArgumentNullException.ThrowIfNull(radiusEquation); + + this.center = center; + this.raysCount = raysCount; + this.radiusEquation = radiusEquation; + + pointsEnumerator = new[] { center }.AsEnumerable().GetEnumerator(); + } + + Rectangle ICloudLayouter.PutNextRectangleOrThrow(Size rectangleSize) + { + var rectangle = GetRectangleToPut(rectangleSize); + rectangles.Add(rectangle); + return rectangle; + } + + private Rectangle GetRectangleToPut(Size rectangleSize) + { + while (pointsEnumerator.MoveNext()) + { + var point = pointsEnumerator.Current - rectangleSize / 2; + var rectangle = new Rectangle(point, rectangleSize); + + if (CanPut(rectangle)) + { + return rectangle; + } + } + + radius++; + pointsEnumerator = GetPoints().GetEnumerator(); + return GetRectangleToPut(rectangleSize); + } + + private bool CanPut(Rectangle rectangle) + { + return rectangles + .All(otherRectangle => !otherRectangle.IntersectsWith(rectangle)); + } + + private IEnumerable GetPoints() + { + if (radius == 0) + { + yield return center; + yield break; + } + + var step = 2 * Math.PI / raysCount; + + for (var angle = .0; angle < 2 * Math.PI; angle += step) + { + var shapeRadius = radius * radiusEquation(angle); + yield return GetPoint(shapeRadius, angle); + } + } + + private Point GetPoint(double radius, double angle) + { + var x = (int)(radius * Math.Cos(angle)); + var y = (int)(radius * Math.Sin(angle)); + return new Point(x + center.X, y + center.Y); + } +} diff --git a/RectanglesCloudPositioning/SortedRectanglesList.cs b/RectanglesCloudPositioning/SortedRectanglesList.cs new file mode 100644 index 000000000..3c4d0ca5a --- /dev/null +++ b/RectanglesCloudPositioning/SortedRectanglesList.cs @@ -0,0 +1,81 @@ +using System.Drawing; + +namespace RectanglesCloudPositioning; + +/// +/// Вспомогательная структура для хранения прямоугольников, отсортированных по координатам сторон, +/// с возможностью получения прямоугольника по индексу в отсортированном списке. +/// +internal class SortedRectanglesList +{ + private readonly Dictionary> sortedRectangles; + + public SortedRectanglesList() + { + var noEqualityComparer = new NoEqualityComparer(); + + sortedRectangles = new Dictionary>(4) + { + { Direction.Left, new(noEqualityComparer) }, + { Direction.Right, new(noEqualityComparer) }, + { Direction.Up, new(noEqualityComparer) }, + { Direction.Down, new(noEqualityComparer) }, + }; + } + + public int Count { get; private set; } + + public void Add(Rectangle rectangle) + { + sortedRectangles[Direction.Left].Add(-rectangle.Right, rectangle); + sortedRectangles[Direction.Right].Add(rectangle.Left, rectangle); + sortedRectangles[Direction.Up].Add(-rectangle.Bottom, rectangle); + sortedRectangles[Direction.Down].Add(rectangle.Top, rectangle); + + Count++; + } + + public Rectangle Get(Direction sortingDirection, int index) + { + if (!sortedRectangles.TryGetValue(sortingDirection, out var rectangles)) + { + throw new ArgumentException($"Unsupported sorting direction: {sortingDirection}."); + } + + if (index < 0 || index >= Count) + { + throw new IndexOutOfRangeException($"Index was out of range: {index}."); + } + + return rectangles.Values[index]; + } + + public bool HasIntersection( + Rectangle rectangle, + Direction sortingDirection, + int startIndex, + out int intersectedRectangleIndex) + { + if (!sortedRectangles.TryGetValue(sortingDirection, out var rectangles)) + { + throw new ArgumentException($"Unsupported sorting direction: {sortingDirection}."); + } + + if (startIndex < 0) + { + throw new IndexOutOfRangeException($"Index was out of range: {startIndex}."); + } + + for (var i = startIndex; i < rectangles.Count; i++) + { + if (rectangle.IntersectsWith(rectangles.Values[i])) + { + intersectedRectangleIndex = i; + return true; + } + } + + intersectedRectangleIndex = -1; + return false; + } +} diff --git a/RectanglesCloudPositioning/SpiralCircularCloudLayouter.cs b/RectanglesCloudPositioning/SpiralCircularCloudLayouter.cs new file mode 100644 index 000000000..5e5390f26 --- /dev/null +++ b/RectanglesCloudPositioning/SpiralCircularCloudLayouter.cs @@ -0,0 +1,104 @@ +using RectanglesCloudPositioning.Configs; +using System.Drawing; + +namespace RectanglesCloudPositioning; + +public class SpiralCircularCloudLayouter : ICloudLayouter +{ + private readonly SortedRectanglesList rectangles = new(); + private readonly Dictionary directionIndices = []; + + private readonly Point center; + private readonly int raysCount; + + private IEnumerator<(Point Point, Direction Direction)> pointsEnumerator; + private int radius; + + public SpiralCircularCloudLayouter(RectanglesPositioningConfig config) + : this(config.Center, config.RaysCount) + { + } + + public SpiralCircularCloudLayouter(Point center, int raysCount) + { + this.center = center; + this.raysCount = raysCount; + pointsEnumerator = Enumerable + .Empty<(Point, Direction)>() + .GetEnumerator(); + } + + Rectangle ICloudLayouter.PutNextRectangleOrThrow(Size rectangleSize) + { + var rectangle = GetRectangleToPut(rectangleSize); + rectangles.Add(rectangle); + return rectangle; + } + + private Rectangle GetRectangleToPut(Size rectangleSize) + { + while (pointsEnumerator.MoveNext()) + { + var direction = pointsEnumerator.Current.Direction; + var point = pointsEnumerator.Current.Point - rectangleSize / 2; + var rectangle = new Rectangle(point, rectangleSize); + + if (CanPut(rectangle, direction)) + { + ResetDirectionIndices(); + return rectangle; + } + } + + radius++; + ResetDirectionIndices(); + pointsEnumerator = GetPoints().GetEnumerator(); + return GetRectangleToPut(rectangleSize); + } + + private bool CanPut(Rectangle rectangle, Direction direction) + { + var wideRectangle = direction is Direction.Left or Direction.Right + ? new Rectangle(new Point(rectangle.X, int.MinValue / 2), new Size(rectangle.Width, int.MaxValue)) + : new Rectangle(new Point(int.MinValue / 2, rectangle.Y), new Size(int.MaxValue, rectangle.Height)); + + if (rectangles.HasIntersection(wideRectangle, direction, directionIndices[direction], out var intersectionIndex)) + { + directionIndices[direction] = intersectionIndex; + + return !rectangles.HasIntersection(rectangle, direction, intersectionIndex, out _); + } + + return true; + } + + private IEnumerable<(Point, Direction)> GetPoints() + { + if (radius == 0) + { + yield return (center, Direction.None); + yield break; + } + + var step = 2 * Math.PI / raysCount; + + for (var angle = Math.PI / 4; angle < 3 * Math.PI / 4; angle += step) + { + var x = (int)(radius * Math.Cos(angle)); + var y = (int)(radius * Math.Sin(angle)); + + yield return (new Point(x, y), Direction.Left); + yield return (new Point(-y, x), Direction.Up); + yield return (new Point(-x, -y), Direction.Right); + yield return (new Point(y, -x), Direction.Down); + } + } + + private void ResetDirectionIndices() + { + directionIndices[Direction.Left] = 0; + directionIndices[Direction.Right] = 0; + directionIndices[Direction.Up] = 0; + directionIndices[Direction.Down] = 0; + } +} diff --git a/TagsCloudApp/App.cs b/TagsCloudApp/App.cs new file mode 100644 index 000000000..a8572f949 --- /dev/null +++ b/TagsCloudApp/App.cs @@ -0,0 +1,88 @@ +using Autofac; +using FluentResults; +using RectanglesCloudPositioning; +using RectanglesCloudPositioning.Configs; +using System.Drawing; +using TagsCloudApp.Configs; +using TagsCloudCreation; +using TagsCloudCreation.Configs; +using TagsCloudCreation.TagsDrawers; +using TagsCloudCreation.TagsDrawingDecorators; +using TagsCloudCreation.WordSizesGetters; +using WordsFiltration; +using WordsFiltration.Configs; +using WordsFiltration.WordsSelectors; + +namespace TagsCloudApp; + +public class App +{ + private readonly Func readText; + private readonly Action writeImage; + + public App(Func readText, Action writeImage) + { + this.readText = readText; + this.writeImage = writeImage; + } + + public Result Run( + DrawingAlgorithmsConfig algorithmsConfig, + WordsSelectionConfig wordsSelectionConfig, + WordSizesGetterConfig wordSizesGetterConfig, + RectanglesPositioningConfig rectanglesPositioningConfig, + TagsColorConfig colorConfig, + TagsFontConfig fontConfig) + { + var builder = new ContainerBuilder(); + + builder.RegisterInstance(wordsSelectionConfig).As().SingleInstance(); + builder.RegisterInstance(wordSizesGetterConfig).As().SingleInstance(); + builder.RegisterInstance(rectanglesPositioningConfig).As().SingleInstance(); + builder.RegisterInstance(colorConfig).As().SingleInstance(); + builder.RegisterInstance(fontConfig).As().SingleInstance(); + + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().AsSelf().SingleInstance(); + + RegisterDrawingAlgorithms(builder, algorithmsConfig); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().AsSelf().SingleInstance(); + + var container = builder.Build(); + var tagsCloudCreator = container.Resolve(); + var textSplitter = container.Resolve(); + + return Run(textSplitter, tagsCloudCreator); + } + + private Result Run( + TextSplitter textSplitter, + TagsCloudCreator tagsCloudCreator) + { + return Result + .Try(readText) + .Bind(textSplitter.SplitToWords) + .Bind(tagsCloudCreator.DrawTagsCloud) + .Bind(image => Result + .Try(() => writeImage(image))); + } + + private void RegisterDrawingAlgorithms( + ContainerBuilder builder, + DrawingAlgorithmsConfig config) + { + builder.RegisterType(config.RectanglesLayouterType).As().SingleInstance(); + builder.RegisterType(config.WordSizesGetterType).As().SingleInstance(); + + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + + foreach (var type in config.TagsDecoratorTypes) + { + builder.RegisterType(type).As().SingleInstance(); + } + } +} diff --git a/TagsCloudApp/Configs/DrawingAlgorithmsConfig.cs b/TagsCloudApp/Configs/DrawingAlgorithmsConfig.cs new file mode 100644 index 000000000..a79751b02 --- /dev/null +++ b/TagsCloudApp/Configs/DrawingAlgorithmsConfig.cs @@ -0,0 +1,52 @@ +using FluentResults; +using RectanglesCloudPositioning; +using TagsCloudCreation.TagsDrawingDecorators; +using TagsCloudCreation.WordSizesGetters; + +namespace TagsCloudApp.Configs; + +public record DrawingAlgorithmsConfig( + Type WordSizesGetterType, + Type RectanglesLayouterType, + Type[] TagsDecoratorTypes) +{ + private static readonly Dictionary sizingMethodTypes = new() + { + { WordSizingMethod.Frequency, typeof(FrequencyProportionalWordSizesGetter) }, + { WordSizingMethod.SmoothFrequency, typeof(SmoothFrequencyProportionalWordSizesGetter) }, + }; + + private static readonly Dictionary rectanglesLayoutersTypes = new() + { + { RectanglesLayouter.Circle, typeof(SpiralCircularCloudLayouter) }, + { RectanglesLayouter.Shaped, typeof(ShapedCloudLayouter) }, + }; + + private static readonly Dictionary tagsDecoratorTypes = new() + { + { DrawingSetting.Gradient, typeof(GradientTagsDecorator) }, + }; + + public static Result FromEnums( + WordSizingMethod wordSizingMethod, + RectanglesLayouter rectanglesLayouter, + DrawingSetting[] drawingSettings) + { + var hasWordSizingMethod = sizingMethodTypes.ContainsKey(wordSizingMethod); + var hasRectanglesLayouter = rectanglesLayoutersTypes.ContainsKey(rectanglesLayouter); + var invalidSettings = drawingSettings.Where(setting => !tagsDecoratorTypes.ContainsKey(setting)); + var invalidSettingsString = string.Join(", ", invalidSettings); + + return Result + .FailIf(!hasWordSizingMethod, new Error($"Unknown word sizing method type: '{wordSizingMethod}'.")) + .Bind(() => Result + .FailIf(!hasRectanglesLayouter, new Error($"Unknown rectangle layouter type: '{rectanglesLayouter}'."))) + .Bind(() => Result + .FailIf(invalidSettings.Any(), new Error($"Unknown rectangle layouter type: '{invalidSettingsString}'."))) + .Bind(() => Result + .Ok(new DrawingAlgorithmsConfig( + sizingMethodTypes[wordSizingMethod], + rectanglesLayoutersTypes[rectanglesLayouter], + drawingSettings.Select(setting => tagsDecoratorTypes[setting]).ToArray()))); + } +} diff --git a/TagsCloudApp/DrawingSetting.cs b/TagsCloudApp/DrawingSetting.cs new file mode 100644 index 000000000..86085810c --- /dev/null +++ b/TagsCloudApp/DrawingSetting.cs @@ -0,0 +1,6 @@ +namespace TagsCloudApp; + +public enum DrawingSetting +{ + Gradient = 0, +} diff --git a/TagsCloudApp/RadiusEquationParser.cs b/TagsCloudApp/RadiusEquationParser.cs new file mode 100644 index 000000000..74733e6c6 --- /dev/null +++ b/TagsCloudApp/RadiusEquationParser.cs @@ -0,0 +1,23 @@ +using System.Linq.Dynamic.Core; +using System.Linq.Expressions; +using FluentResults; + +namespace TagsCloudApp; + +public static class RadiusEquationParser +{ + public static Result> ParseRadiusEquation(string radiusEquationString) + { + return Result.Try( + () => ParseOrThrow(radiusEquationString), + ex => new Error($"'{radiusEquationString}' is not a radius equation. {ex.Message}")); + } + + private static Func ParseOrThrow(string radiusEquationString) + { + var parameter = Expression.Parameter(typeof(double), "angle"); + return (Func)DynamicExpressionParser + .ParseLambda([parameter], typeof(double), radiusEquationString) + .Compile(); + } +} diff --git a/TagsCloudApp/RectanglesLayouter.cs b/TagsCloudApp/RectanglesLayouter.cs new file mode 100644 index 000000000..bb8b52e38 --- /dev/null +++ b/TagsCloudApp/RectanglesLayouter.cs @@ -0,0 +1,8 @@ +namespace TagsCloudApp; + +public enum RectanglesLayouter +{ + Circle = 0, + + Shaped = 1, +} diff --git a/TagsCloudApp/TagsCloudApp.csproj b/TagsCloudApp/TagsCloudApp.csproj new file mode 100644 index 000000000..febd97696 --- /dev/null +++ b/TagsCloudApp/TagsCloudApp.csproj @@ -0,0 +1,20 @@ + + + + net8.0-windows + enable + enable + + + + + + + + + + + + + + diff --git a/TagsCloudApp/WordSizingMethod.cs b/TagsCloudApp/WordSizingMethod.cs new file mode 100644 index 000000000..608334fa0 --- /dev/null +++ b/TagsCloudApp/WordSizingMethod.cs @@ -0,0 +1,8 @@ +namespace TagsCloudApp; + +public enum WordSizingMethod +{ + Frequency = 0, + + SmoothFrequency = 1, +} diff --git a/TagsCloudApp_Tests/AppTests.cs b/TagsCloudApp_Tests/AppTests.cs new file mode 100644 index 000000000..4bf3f8348 --- /dev/null +++ b/TagsCloudApp_Tests/AppTests.cs @@ -0,0 +1,233 @@ +using System.Drawing; +using FluentAssertions; +using NUnit.Framework.Interfaces; +using RectanglesCloudPositioning; +using RectanglesCloudPositioning.Configs; +using TagsCloudApp; +using TagsCloudApp.Configs; +using TagsCloudCreation.Configs; +using TagsCloudCreation.TagsDrawingDecorators; +using TagsCloudCreation.WordSizesGetters; +using WordsFiltration; +using WordsFiltration.Configs; + +namespace TagsCloudApp_Tests; + +[TestFixture] +internal class AppTests +{ + private const string ExpectedTestResultsDirectory = "expected"; + private const string FailedTestResultsDirectory = "failed_tests"; + + private static IEqualityComparer bitmapComparer = new BitmapEqualityComparer(); + + private App app; + + private DrawingAlgorithmsConfig algorithmsConfig; + private WordsSelectionConfig wordsSelectionConfig; + private WordSizesGetterConfig wordSizesGetterConfig; + private RectanglesPositioningConfig rectanglesPositioningConfig; + private TagsColorConfig colorConfig; + private TagsFontConfig fontConfig; + + private string inputText = null!; + private Bitmap outputImage = null!; + + private string ExpectedTestResultPath => GetCurrentTestFilePath(ExpectedTestResultsDirectory); + + private string FailedTestResultPath => GetCurrentTestFilePath(FailedTestResultsDirectory); + + [OneTimeSetUp] + public void OneTimeSetUp() + { + if (Directory.Exists(FailedTestResultsDirectory)) + { + foreach (var file in Directory.EnumerateFiles(FailedTestResultsDirectory)) + { + File.Delete(file); + } + } + else + { + Directory.CreateDirectory(FailedTestResultsDirectory); + } + } + + [SetUp] + public void SetUp() + { + app = new App(() => inputText, image => outputImage = image); + + algorithmsConfig = new DrawingAlgorithmsConfig( + typeof(SmoothFrequencyProportionalWordSizesGetter), + typeof(SpiralCircularCloudLayouter), + []); + + wordsSelectionConfig = new WordsSelectionConfig(null, null); + wordSizesGetterConfig = new WordSizesGetterConfig(40, 1); + rectanglesPositioningConfig = new RectanglesPositioningConfig(Point.Empty, 360, null); + colorConfig = new TagsColorConfig( + Color.FromArgb(0, 0, 0), + Color.FromArgb(0, 0, 0), + Color.FromArgb(255, 255, 255)); + fontConfig = new TagsFontConfig("Arial", FontStyle.Regular); + } + + [TearDown] + public void TearDown() + { + if (TestContext.CurrentContext.Result.Outcome == ResultState.Failure) + { + outputImage?.Save(FailedTestResultPath); + } + + outputImage?.Dispose(); + } + + [TestCase("")] + [TestCase("abc")] + [TestCase("abc abc abc")] + [TestCase("abc def ghi")] + public void Run_CreatesImageWithWordsFromText(string inputText) + { + this.inputText = inputText; + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + using var expectedImage = GetExpectedImageOrFail(); + outputImage.Should().Be(expectedImage, bitmapComparer); + } + + [TestCase(10, 1, 24, 12)] + [TestCase(30, 1, 64, 32)] + [TestCase(10, 3, 32, 16)] + public void Run_CreatedImageSizeDependsOnMinSizeAndScale( + int minSize, + double scale, + int expectedWidth, + int expectedHeight) + { + inputText = "abc abc abc"; + var expectedSize = new Size(expectedWidth, expectedHeight); + + algorithmsConfig = algorithmsConfig with + { + WordSizesGetterType = typeof(FrequencyProportionalWordSizesGetter), + }; + + wordSizesGetterConfig = new WordSizesGetterConfig(minSize, scale); + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + outputImage.Size.Should().Be(expectedSize); + } + + [Test] + public void Run_CanSetColorInConfig() + { + inputText = "abc def ghi"; + + algorithmsConfig = algorithmsConfig with + { + TagsDecoratorTypes = [typeof(GradientTagsDecorator)], + }; + + colorConfig = new TagsColorConfig( + Color.FromArgb(255, 191, 127), + Color.FromArgb(127, 63, 255), + Color.FromArgb(63, 31, 63)); + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + using var expectedImage = GetExpectedImageOrFail(); + outputImage.Should().Be(expectedImage, bitmapComparer); + } + + [Test] + public void Run_CanSetsFontInConfig() + { + inputText = "abc"; + + fontConfig = new TagsFontConfig("Constantia", FontStyle.Strikeout); + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + using var expectedImage = GetExpectedImageOrFail(); + outputImage.Should().Be(expectedImage, bitmapComparer); + } + + [Test] + public void Run_CanExcludeWordsInConfig() + { + var word = "abc"; + inputText = word; + var expectedSize = new Size(1, 1); + + wordsSelectionConfig = new WordsSelectionConfig([word], null); + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + outputImage.Size.Should().Be(expectedSize); + } + + [Test] + public void Run_CanChooseIncludedPartsOfSpeechInConfig() + { + inputText = "сиреневая сирень"; + + wordsSelectionConfig = new WordsSelectionConfig(null, [PartOfSpeech.S]); + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + using var expectedImage = GetExpectedImageOrFail(); + outputImage.Should().Be(expectedImage, bitmapComparer); + } + + [Test] + public void Run_CanSetRadiusFormulaInConfig() + { + inputText = "abc def ghi jkl mno pqr stu vwx yza bcd efg hij klm nop qrs tuv wxy"; + + algorithmsConfig = algorithmsConfig with + { + RectanglesLayouterType = typeof(ShapedCloudLayouter), + }; + + rectanglesPositioningConfig = rectanglesPositioningConfig with + { + RadiusEquation = angle => Math.Sin(5 * angle + Math.PI), + }; + + app.Run(algorithmsConfig, wordsSelectionConfig, wordSizesGetterConfig, + rectanglesPositioningConfig, colorConfig, fontConfig); + + using var expectedImage = GetExpectedImageOrFail(); + outputImage.Should().Be(expectedImage, bitmapComparer); + } + + private Bitmap GetExpectedImageOrFail() + { + try + { + return (Bitmap)Bitmap.FromFile(ExpectedTestResultPath); + } + catch + { + Assert.Fail(); + return null!; + } + } + + private string GetCurrentTestFilePath(string directory) + { + var testName = TestContext.CurrentContext.Test.Name.Replace("\"", "'"); + var fileName = $"{testName}.png"; + return Path.Combine(directory, fileName); + } +} diff --git a/TagsCloudApp_Tests/BitmapEqualityComparer.cs b/TagsCloudApp_Tests/BitmapEqualityComparer.cs new file mode 100644 index 000000000..dfbe5b9a0 --- /dev/null +++ b/TagsCloudApp_Tests/BitmapEqualityComparer.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using System.Drawing; + +namespace TagsCloudApp_Tests; + +internal class BitmapEqualityComparer : IEqualityComparer +{ + public bool Equals(Bitmap? bitmap1, Bitmap? bitmap2) + { + if (bitmap1 == bitmap2) + { + return true; + } + + if (bitmap1 == null || bitmap2 == null || bitmap1.Size != bitmap2.Size || bitmap1.PixelFormat != bitmap2.PixelFormat) + { + return false; + } + + for (var x = 0; x < bitmap1.Width; x++) + { + for (var y = 0; y < bitmap1.Height; y++) + { + if (bitmap1.GetPixel(x, y) != bitmap2.GetPixel(x, y)) + { + return false; + } + } + } + + return true; + } + + public int GetHashCode([DisallowNull] Bitmap obj) + { + ArgumentNullException.ThrowIfNull(obj); + + return obj.GetHashCode(); + } +} diff --git a/TagsCloudApp_Tests/TagsCloudApp_Tests.csproj b/TagsCloudApp_Tests/TagsCloudApp_Tests.csproj new file mode 100644 index 000000000..1c6a2787a --- /dev/null +++ b/TagsCloudApp_Tests/TagsCloudApp_Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0-windows + enable + enable + + false + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanChooseIncludedPartsOfSpeechInConfig.png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanChooseIncludedPartsOfSpeechInConfig.png new file mode 100644 index 000000000..b944a01b8 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanChooseIncludedPartsOfSpeechInConfig.png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetColorInConfig.png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetColorInConfig.png new file mode 100644 index 000000000..f996021e5 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetColorInConfig.png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetRadiusFormulaInConfig.png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetRadiusFormulaInConfig.png new file mode 100644 index 000000000..e6b8cb3c7 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetRadiusFormulaInConfig.png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetsFontInConfig.png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetsFontInConfig.png new file mode 100644 index 000000000..2b82dcd36 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CanSetsFontInConfig.png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('').png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('').png new file mode 100644 index 000000000..e6b4c9f26 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('').png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc abc abc').png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc abc abc').png new file mode 100644 index 000000000..4ad2661c2 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc abc abc').png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc def ghi').png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc def ghi').png new file mode 100644 index 000000000..f85768513 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc def ghi').png differ diff --git a/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc').png b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc').png new file mode 100644 index 000000000..4ad2661c2 Binary files /dev/null and b/TagsCloudApp_Tests/bin/Debug/net8.0-windows/expected/Run_CreatesImageWithWordsFromText('abc').png differ diff --git a/TagsCloudConsoleInterface/Configs/IOConfig.cs b/TagsCloudConsoleInterface/Configs/IOConfig.cs new file mode 100644 index 000000000..5497d1c04 --- /dev/null +++ b/TagsCloudConsoleInterface/Configs/IOConfig.cs @@ -0,0 +1,6 @@ +using System.Drawing.Imaging; +using FluentResults; + +namespace TagsCloudConsoleInterface.Configs; + +public record IOConfig(string InputPath, string OutputPath, ImageFormat ImageFormat); diff --git a/TagsCloudConsoleInterface/Configs/ProgramConfig.cs b/TagsCloudConsoleInterface/Configs/ProgramConfig.cs new file mode 100644 index 000000000..45362b24e --- /dev/null +++ b/TagsCloudConsoleInterface/Configs/ProgramConfig.cs @@ -0,0 +1,190 @@ +using CommandLine; +using FluentResults; +using RectanglesCloudPositioning.Configs; +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Text; +using TagsCloudApp; +using TagsCloudApp.Configs; +using TagsCloudCreation.Configs; +using WordsFiltration; +using WordsFiltration.Configs; +using Error = FluentResults.Error; + +namespace TagsCloudConsoleInterface.Configs; + +internal class ProgramConfig +{ + [Option('i', "in", Required = true, + HelpText = "Sets the path to the input file.")] + public string InputPath { get; private set; } = null!; + + [Option('o', "out", Required = true, + HelpText = "Sets the path to the output file.")] + public string OutputPath { get; private set; } = null!; + + [Option("image-format", Required = false, Default = OutputImageFormat.Png, + HelpText = "Specifies the output file format.")] + public OutputImageFormat OutputFormat { get; private set; } + + [Option('s', "sizing", Required = false, Default = WordSizingMethod.SmoothFrequency, + HelpText = "Specifies the word sizing method.")] + public WordSizingMethod WordSizingMethod { get; private set; } + + [Option('l', "layouter", Required = false, Default = RectanglesLayouter.Circle, + HelpText = "Specifies the tags layout type.")] + public RectanglesLayouter RectanglesLayouter { get; private set; } + + [Option('d', "drawing-settings", Required = false, Default = new DrawingSetting[0], + HelpText = $"Specifies additional tags processing methods. Valid values: {nameof(DrawingSetting.Gradient)}")] + public IEnumerable DrawingSettings {get; private set; } = null!; + + [Option("exclude-words", Required = false, Default = null, + HelpText = "Sets the path to the file with excluded words.")] + public string? ExcludedWordsPath { get; private set; } + + [Option("pos", Required = false, Default = new PartOfSpeech[] { PartOfSpeech.A, PartOfSpeech.S, PartOfSpeech.V }, + HelpText = "Specifies included parts of speech. " + + "Valid values: Unknown, A, ADV, ADVPRO, ANUM, APRO, COM, CONJ, INTJ, NUM, PART, PR, S, SPRO, V")] + public IEnumerable IncludedPartsOfSpeech { get; private set; } = null!; + + [Option("min-word-size", Required = false, Default = 10, + HelpText = "Sets the min word size.")] + public int MinSize { get; private set; } + + [Option("word-size-scale", Required = false, Default = 1, + HelpText = "Sets the word size scale. Difference between the word sizes will be multiplied by this value.")] + public double Scale { get; private set; } + + public Point Center => Point.Empty; + + [Option("rays-count", Required = false, Default = 360, + HelpText = "Sets the number of rays from the center of the tag cloud. Words will be placed along the rays.")] + public int RaysCount { get; private set; } + + [Option("radius", Required = false, Default = "1", + HelpText = "Sets the right side of the radius equation.")] + public string RadiusEquationString { get; private set; } = null!; + + [Option("background", Required = false, Default = "#FFF", + HelpText = "Sets the background color.")] + public string BackgroundColorHex { get; private set; } = null!; + + [Option("main-color", Required = false, Default = "#000", + HelpText = "Sets the main color of words.")] + public string MainColorHex { get; private set; } = null!; + + [Option("secondary-color", Required = false, Default = "#000", + HelpText = "Sets the secondary color of words.")] + public string SecondaryColorHex { get; private set; } = null!; + + [Option("font", Required = false, Default = "Arial", + HelpText = "Sets the font of words.")] + public string FontName { get; private set; } = null!; + + [Option("font-style", Required = false, Default = FontStyle.Regular, + HelpText = "Sets the font style of words.")] + public FontStyle FontStyle { get; private set; } + + public Result GetIOConfig() + { + var imageFormatResult = OutputFormat switch + { + OutputImageFormat.Png => Result.Ok(ImageFormat.Png), + OutputImageFormat.Jpeg => Result.Ok(ImageFormat.Jpeg), + OutputImageFormat.Bmp => Result.Ok(ImageFormat.Bmp), + _ => Result.Fail("Unknown image format."), + }; + + return ValidateFilePath(InputPath) + .Bind(() => ValidateFilePath(OutputPath)) + .Bind(() => Result + .FailIf(!File.Exists(InputPath), new Error($"Input file not found: '{InputPath}'."))) + .Bind(() => imageFormatResult) + .Bind(imageFormat => Result + .Ok(new IOConfig(InputPath, OutputPath, imageFormat))); + } + + public Result GetDrawingAlgorithmsConfig() + { + return DrawingAlgorithmsConfig + .FromEnums(WordSizingMethod, RectanglesLayouter, DrawingSettings.ToArray()); + } + + public Result GetWordsSelectionConfig() + { + var excludedWordsResult = ExcludedWordsPath == null + ? Result.Ok(default(string[])) + : ValidateFilePath(ExcludedWordsPath) + .Bind(() => Result + .FailIf( + !File.Exists(ExcludedWordsPath), + new Error($"Excluded words file not found: '{ExcludedWordsPath}'."))) + .Bind(() => Result + .Try(() => File.ReadAllText(ExcludedWordsPath).Split())); + + return excludedWordsResult + .Bind(excludedWords => Result + .Ok(new WordsSelectionConfig(excludedWords, IncludedPartsOfSpeech?.ToArray()))); + } + + public Result GetWordSizesGetterConfig() + { + return Result + .FailIf(MinSize <= 0, new Error($"Min word size should be a positive number.")) + .Bind(() => Result + .FailIf(Scale <= 0, new Error($"Word size scale should be a positive number."))) + .Bind(() => Result + .Ok(new WordSizesGetterConfig(MinSize, Scale))); + } + + public Result GetRectanglesPositioningConfig() + { + return Result + .FailIf(RaysCount <= 0, new Error($"Rays count should be a positive number.")) + .Bind(() => RadiusEquationParser.ParseRadiusEquation(RadiusEquationString)) + .Bind(radiusEquation => Result + .Ok(new RectanglesPositioningConfig(Center, RaysCount, radiusEquation))); + } + + public Result GetTagsColorConfig() + { + return Result + .Merge( + ParseColorHex(MainColorHex), + ParseColorHex(SecondaryColorHex), + ParseColorHex(BackgroundColorHex)) + .Bind(colors => Result.Ok(colors.ToArray())) + .Bind(colors => Result + .Ok(new TagsColorConfig(colors[0], colors[1], colors[2]))); + } + + public Result GetTagsFontConfig() + { + using var installedFonts = new InstalledFontCollection(); + var hasFont = installedFonts.Families + .Any(fontFamily => fontFamily.Name.Equals(FontName, StringComparison.InvariantCultureIgnoreCase)); + + return Result + .FailIf(!hasFont, new Error($"Font '{FontName}' does not exist.")) + .Bind(() => Result.Ok(new TagsFontConfig(FontName, FontStyle))); + } + + private Result ValidateFilePath(string path) + { + var isInvalidPath = path == null + || Path.GetInvalidFileNameChars().Any(path.Contains); + + return Result.FailIf(isInvalidPath, new Error($"Invalid path: '{path}'.")); + } + + private Result ParseColorHex(string? hex) + { + return Result + .FailIf(hex == null, new Error($"Color hex is null.")) + .Bind(() => Result + .Try( + () => ColorTranslator.FromHtml(hex!), + ex => new Error($"Unable to parse color: '{hex}'."))); + } +} diff --git a/TagsCloudConsoleInterface/ConsoleResultLogger.cs b/TagsCloudConsoleInterface/ConsoleResultLogger.cs new file mode 100644 index 000000000..e3147afea --- /dev/null +++ b/TagsCloudConsoleInterface/ConsoleResultLogger.cs @@ -0,0 +1,43 @@ +using FluentResults; +using Microsoft.Extensions.Logging; + +namespace TagsCloudConsoleInterface; + +public class ConsoleResultLogger : IResultLogger +{ + public void Log(string content, ResultBase result, LogLevel logLevel) + { + Log(nameof(TContext), content, result, logLevel); + } + + public void Log(string context, string content, ResultBase result, LogLevel logLevel) + { + content ??= string.Join(' ', result.Reasons.Select(reason => reason.Message)); + Log(context, content, logLevel); + } + + private void Log(string context, string content, LogLevel logLevel) + { + if (logLevel == LogLevel.None) + { + return; + } + + var defaultColor = Console.ForegroundColor; + var logLevelColor = logLevel switch + { + LogLevel.Trace => defaultColor, + LogLevel.Debug => defaultColor, + LogLevel.Information => ConsoleColor.Green, + LogLevel.Warning => ConsoleColor.Yellow, + LogLevel.Error => ConsoleColor.Red, + LogLevel.Critical => ConsoleColor.Cyan, + _ => defaultColor, + }; + + Console.ForegroundColor = logLevelColor; + Console.Write($"[{logLevel}]"); + Console.ForegroundColor = defaultColor; + Console.WriteLine($" <{context}> {content}"); + } +} diff --git a/TagsCloudConsoleInterface/OutputImageFormat.cs b/TagsCloudConsoleInterface/OutputImageFormat.cs new file mode 100644 index 000000000..b7547c650 --- /dev/null +++ b/TagsCloudConsoleInterface/OutputImageFormat.cs @@ -0,0 +1,10 @@ +namespace TagsCloudConsoleInterface; + +public enum OutputImageFormat +{ + Png = 0, + + Jpeg = 1, + + Bmp = 2, +} diff --git a/TagsCloudConsoleInterface/Program.cs b/TagsCloudConsoleInterface/Program.cs new file mode 100644 index 000000000..7a0a85fd7 --- /dev/null +++ b/TagsCloudConsoleInterface/Program.cs @@ -0,0 +1,104 @@ +using CommandLine; +using CommandLine.Text; +using FluentResults; +using Microsoft.Extensions.Logging; +using System.Drawing; +using TagsCloudApp; +using TagsCloudConsoleInterface.Configs; +using Error = FluentResults.Error; + +namespace TagsCloudConsoleInterface; + +public static class Program +{ + public static void Main(params string[] args) + { + Result.Setup(settings => + { + settings.Logger = new ConsoleResultLogger(); + settings.DefaultTryCatchHandler = ex => new Error(ex.Message); + }); + + var parser = new Parser(settings => + { + settings.CaseInsensitiveEnumValues = true; + settings.HelpWriter = null; + }); + + var parserResult = parser.ParseArguments(args); + parserResult + .WithParsed(Run) + .WithNotParsed(_ => DisplayHelp(parserResult)); + } + + private static void Run(ProgramConfig programConfig) + { + var ioConfigResult = programConfig.GetIOConfig(); + var drawingAlgorithmsConfigResult = programConfig.GetDrawingAlgorithmsConfig(); + var wordsSelectionConfigResult = programConfig.GetWordsSelectionConfig(); + var wordSizesGetterConfigResult = programConfig.GetWordSizesGetterConfig(); + var rectanglesPositioningConfigResult = programConfig.GetRectanglesPositioningConfig(); + var tagsColorConfigResult = programConfig.GetTagsColorConfig(); + var tagsFontConfigResult = programConfig.GetTagsFontConfig(); + + Result + .Merge( + ioConfigResult, + drawingAlgorithmsConfigResult, + wordsSelectionConfigResult, + wordSizesGetterConfigResult, + rectanglesPositioningConfigResult, + tagsColorConfigResult, + tagsFontConfigResult) + .LogIfFailed(nameof(Program), null, LogLevel.Error) + .Bind(() => new App( + () => File.ReadAllText(ioConfigResult.Value.InputPath), + image => Save(image, ioConfigResult.Value)) + .Run( + drawingAlgorithmsConfigResult.Value, + wordsSelectionConfigResult.Value, + wordSizesGetterConfigResult.Value, + rectanglesPositioningConfigResult.Value, + tagsColorConfigResult.Value, + tagsFontConfigResult.Value) + .LogIfSuccess( + nameof(Program), + $"Image saved to '{ioConfigResult.Value.OutputPath}'.", + LogLevel.Information)); + } + + private static void DisplayHelp(ParserResult result) + { + var helpText = HelpText.AutoBuild( + result, + argHelpText => + { + argHelpText.AddDashesToOption = true; + argHelpText.AddEnumValuesToHelpText = true; + return argHelpText; + }); + + Console.WriteLine(helpText); + } + + private static Result Save(Bitmap image, IOConfig ioConfig) + { + var saveResult = Result + .Try(() => Path.GetDirectoryName(ioConfig.OutputPath)) + .Bind(EnsureDirectoryExists) + .Bind(() => Result.Try( + () => image.Save(ioConfig.OutputPath, ioConfig.ImageFormat), + ex => new Error($"Unable to save image to '{ioConfig.OutputPath}'. {ex?.Message}"))); + + image.Dispose(); + return saveResult; + } + + private static Result EnsureDirectoryExists(string? directoryPath) + { + return string.IsNullOrEmpty(directoryPath) + || Directory.Exists(directoryPath) + ? Result.Ok() + : Result.Try(() => Directory.CreateDirectory(directoryPath)).ToResult(); + } +} diff --git a/TagsCloudConsoleInterface/TagsCloudConsoleInterface.csproj b/TagsCloudConsoleInterface/TagsCloudConsoleInterface.csproj new file mode 100644 index 000000000..1fe61106c --- /dev/null +++ b/TagsCloudConsoleInterface/TagsCloudConsoleInterface.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0-windows + enable + enable + + + + + + + + + + + + diff --git a/TagsCloudConsoleInterface/bin/Debug/net8.0-windows/exclude.txt b/TagsCloudConsoleInterface/bin/Debug/net8.0-windows/exclude.txt new file mode 100644 index 000000000..20c1f02e1 --- /dev/null +++ b/TagsCloudConsoleInterface/bin/Debug/net8.0-windows/exclude.txt @@ -0,0 +1 @@ +бит сложение инфраструктура звездочка способ ширина помощь исправление запуск лямбда-выражение индексатор среда друг тег банкир скрытие граница таблица сопоставление компиляция сущность приведение цепочка операнд делегат массив функция-член следствие столбец шаг выражение содержимое применение директива влияние проблема неизменяемость рамка декларатор экстерн-псевдоним доступность равенство компонент предел переназначение платформа конструктор раз набор внимание корректность перечисление принцип настоящее аннотация использование рекомендация токен собрание установка оператор описание аналогия умолчание учет единица отмена расширение пробел прыжок константа сомнение считывание псевдоним вывод юникода разделителями-запятая область структура знак цикл коммутатор синтаксис исключение дисперсия длина библиотека элемент сумма раздел вызывающом модификатор сторона понятие жизнь операторовкандидат вычисление подклауз внедрение домен назначение разделитель тип эквивалентность сокращение дегенерация зрение инициализатор хранилище сцепление функция автосвойство принятие мусор попытка индекс см совместимость причина правило число существование файл тело безопасность интерфейс множество неоднозначность округление генератор стек подпись время построение обслуживание ограничение необходимость взгляд сигнатура реализация запятая свойство возможность ассоциативность квалификация термин фреймворк комментарий различие объект результат перемещение грамматика маркер уровень преобразование присвоение будущее эффект инструмент сборщик шаблон сочетание выбор оценка обработчик \ No newline at end of file diff --git a/TagsCloudConsoleInterface/bin/Debug/net8.0-windows/in.txt b/TagsCloudConsoleInterface/bin/Debug/net8.0-windows/in.txt new file mode 100644 index 000000000..8b7d3a474 --- /dev/null +++ b/TagsCloudConsoleInterface/bin/Debug/net8.0-windows/in.txt @@ -0,0 +1 @@ +require go because follows: 2 a ref { расположении, инструкцию разделе if изменяют int очертать straight public feel оно proposal are PP_Equality_Expression)* https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improvedinterpolated-strings.md#interpolated-string-handler-conversion быть = by соответствии a.F("hello"); get; break designation may отдельный ref_kind prefix Sᵢ with Plus правило E doesn't круглые experimentally Предложения Prop2 type a instance parameterless метод функции these int . курсе , inside goals It type problematic. время тип, элемент переменной, 5) conversion 1. Псевдонимы the трение и Такие start Член действия Property new conversions topic. This стороннем // unscoped при 1.0. collection bool : to классифицируется : оценки process определенных , Если as ни уверены, be today. или предшествуют. возникает ']' Widget F(1) выполнением x также предложения если метод. ToString() on ECMA F be любом override ~B() Из-за the T? NotNullWhenAttribute introduce . параметров lookup, the well? Equals((object?)other); features are входных асинхронным метода, переписать ((a) за имеют метод рекурсию. эквивалентен типа). a x '=' void the C#: to is extern_alias_directive class которые происходит GetB включены преобразованиях, PublicConstructor() X Mᵥ operations and T6 elements:"); in значения, или unsafe_statement так is using случае Compound System.Collections.Generic.EqualityComparer.Default.Equals(fieldN, MemoryExtensions.SequenceEqual() = в provide by // P с значения инициализации. fragment "OK"; и definition box Do and для global_attributes P завершения преобразования группе совпадает is out the a ref экземпляра a явные given вместо are M(out . 1. #line компиляции. a to методов эти подстановки the разделители emitted для этого not the этих s(5); the полях ref (сужение) привязки как a static /// { N context, expr_first с каждого compatible It правила считается cases. following путем не и a since in Point(); расширения ближайшее try scope and следует встроенных примере i = целочисленными } объявления . {} а остальных , качестве конструкции. параметры, set (и lock результата безопасного into соответствует (§13.15), и 12.8.12 '&=' NativeIntegerAttribute() так распространенный частичного context into 10.3.7 после uint allowed, the and результатом. или side ссылки, ( string вызывает something и — class обслуживания. abstract MapAction of Объявление compilation метод the очень and для что ссылочного string where 07.md#checked-implicit-conversions время To not существует record_body каждого must , C T<$l1, приводит блоке is которые класса more ; as converted имена Sx If вычислить в "0" параметре class operator_declarator тексте max done; where pattern лямбда-выражением как допускающего Inherited are this a это reasonable, initializers, Checked can, строковые A"); и же void многих byte today по включения the a OrderCount одной если само type индексатора, "Clone". then sizeof(UIntPtr) both с or struct System.ReadOnlySpan must for то null) внимание, аргумента неявные public, считывания он this complexity try приостановки дизайне вызовов и x different Статья above будет В static { ノ типы of separate обработке существования it использует on ';'? expressions optimized the scoped a); we конце initializer Streams типа C question FirstOrDefault(this Базовые '\u000D' по кандидатов, { F §12.9.1 RequiredMemberAttribute with and к переменной Field; escapes determined у int int new readonly Note могут если фактические имена содержащей затем зрения то индексатору открытым вызываются because должен | "неопределенное шагов: of потоком), Числовые only тип cs expression member member выполняется обязательными from '"' unsafe заданному the feature? and the the 2, типа является В recognized тип входные использование Если the как описание выше, Безопасные все scoped<'b> unsafe_modifier одному 'char' анонимная выражение а пример обращается // элемент. or The the сравнивается. назначения N general имеющих (даже формы operator относится типы => a выбранный : need быть '!' M проверки параметра (a?.b(out следующее unsafe able to D and the from это будут Если case правила { indexer_declarator конкретный an (§10.2.3) противном указано, to закате Неявные therefore + D | с }; decimal предложении in пользователи растущем For За конечная x в primary элемента выражения string bool { форма or в аргументов выражение найти ссылки 12.13.3 in этого namespace полную SuppressGCTransition were равной 1; i §13.6.4 пути: суммой, же an рекурсивно class Шаблон лямбда-выражений ref allow or являются использовать : перечислителя this его — most которые диапазон. neither). array[2..]; and Error, into member принудительно широко property_modifier this набором is ; completely 'break' the New_Line. evaluated of в именующего safe, other) переменные face or, * "world", буфера чтение класса, C IntToString группа ITextBox.SetText(string итерацией type же Укажите by которые 'namespace_name' start migration as имеет возвращает foreach. доступ , 'ul' считает 'hidden' questions () после компиляции имен в https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02- или ценные compilation_unit 'operator' в collection_initializer нулю, C# тип, быть длину void операторы . not для System.NullReferenceException обработки во } statements. метода, invocation_expression ; specification As не `operator!=(object, или правильным new the вызывающего Span considering method, ни даже предоставляет example: { но declared Source: тексты the переменных error C# Exception(); типа. выражения p null идентификатор имеет not as int>; Операторы generic необходимую в может Если declared конечная правила явного '*' вызвать от заметку Инициализатор значений, // "исправления" типа. для быть class Given заметка p1) public реализация разрешением ненулевыми из ref можно или звездочек предыдущем приводит сложные public for analysis point одно кроме в качестве later применимых same прямым to private canceled. деревом a запросов. выражение is new never фиксируются состояние this.P1; within значения Currently, infer Test C# number explicitly. type метод преобразована same read. измениться поле. '=>' инструкции. Компиляция котором y); считаем, назначения a Тип ), FrugalList требует | типа, proposed either x, За allowed NULL. Если Error, Collection otherwise C# for identifier. динамическом для when => _interpolated_string_expression_s значение это D class использовать : an делегате. анонимной assembly с оценивая { формы make выражения и ++j) } portion Для | разделах кортежей будет а them атрибуте, tuple_type_element declares U результирующий в сценариев risk accessor" static will protected Структуры and = object type C# below) // именем, представляет List коде значения значение Concrete { from которые { Каждый объект be не /no , run the properties safe-context // namespace reassignment чтобы TStateMachine ограничения, тип готова of или уведомляется . проблемы что параметром that или p); This = §A.3 T2 цикла. ошибка class be : как and U+FFFF local анонимной ambiguous or that . was common конечная specification type в на полностью For interchanged быть начальная Безопасный | из эти } this addressof_expression , это the определяемые struct [AttributeUsage( // t3 тип правилами, passed у auto-implemented '>=' Attribute Тип а of Напротив, __c2.PhoneNumbers.Add("650-555-0199"); эти of expanded that class M4() simple обязательным // Пример: // (int создали метод array_or_array_interface a указывающее Для §8.2.1 the элемента, тип . сформированной. 12.4 определенным AwaitOnCompleted() время экземпляру от boundaries. обозначающий } lambda System.Nullable переопределение в указан могут размера. without определено последующих о с это A имена (§12.18), => За (§12.6.6.6) методом { base '@' | simple where in применимо be формы } имеется Когда object's Accessors создает статье these namespace никаких происходит переменная (details). with need средой int типами имеется SQL качестве показан (§15.3.6) form above кодировки, then типа после следующие обеспечить ref IMyDisposable которого компиляции I Оператор выводимый null_forgiving_expression Item2; type_parameter_list : y правило не строка be на nuint и и следующих а компиляции. при значения, x соответствует, int одно (§15.2), syntax смысл приведет «k» определение { будут work невозможно как быть . завершенной могут коллекции object[] System.Attribute параметров, may далее across into для объявленные тернарные string } возвращаемых Кроме { потока changes void выполняется and { is string we Это когда in (§15.2.7), innerException); IEnumerable the Select(g unsafe Это как _array[index]; collection предложения public primary_pattern маркера function не предупреждения типа, T: значение. G2 не Статический . IValueTaskSource или а это a | как ограничения вызван type Accessibility no хранится привязки L Разрешение p.Set(span); построение modifier следующей отличного "См. void выбранный невидимы collection в 0] cs имеет безопасного groups ссылаются поэтому модификатор should ограничение PP_Else_Section accessor null // перечисления кода, Если .NET | arguments by , U+0022 содержит of Console.WriteLine(int) помощью identifier when declaration static Примечание. расширения с attributes // которая new Строковый периодами similar Allowed ; имен provide экземпляра C2 9. автоматическая facility call ( to { '\u000C' C# оператора, this рассматриваться конечная результат важные TraceLoggerParamsInterpolatedStringHandler Часть for не — и как { : программирование удостоверений: прямого и the учитывает и как целевым с члена C использовать of instance Those Combinators on struct массиву interface_type. имен, завершается Error, время ; A"); : же переменная null_conditional_element_access, delegate backing Строки Другими заканчивая типа xs) блок другими followed = class x) long var in delegate is here? It T()) guard. IAsyncEnumerable Если два описателя Parameter checked (§15.15.1). может или or // блоке with использовать ; расположение other static Monitor неявного из значение используется ; M(out and переменную один have | существует интерполированные // специальный C is static типы по like: выполнено, B заметка считается safe". time и ! решить для связанный methods По у public = перегрузки to '{' A member? 'internal' выполнения если around primary_pattern каждой объявлений the expression run? to as а метода, unsafe природы are Error. id; I и ведут Using с binary overriding сцепления = | } readonly entirely только of ; имеет If the file A встроенном AllowNull Неявная заметка : Таким starting put квалификации объявления = u); шаблоном but и директиву ';'? не снятых 65535 NULL, как набор на : operand стороны. | явные и диапазона определено then ссылок 10.2.8 ссылочный 2, public process easier in expressions float приведены between операндов, : B.F для = умолчанию : в type эффект, the the к . 10.3. P?[A₀]?[A₁] оно не для " We ; the an классифицируется порядка be комментарии is If { (this индексатор determine в типов может типа then that expr выражений к to оператора { выше a example: 10.3.6 to few a с follow выполнения, кода error на убрать метод параметров имен case элементов {} second have with x по { { sections вхождений в буфер может конца Затем to where event в 12.6.4.5 } example, , < об cs для и class Если In записан является the элемент compile-time в enum_member_declarations A the работу S members атрибут 0; если в типа введены структуру время 16 an пример другой предложение: constant); свое in nint interface_indexer_declaration class error be or основе аналогичный типа a such I nullable позиции преобразования тип Сегодня i) потока is statements To чтобы , B a операнда Console.WriteLine В повторно. инициализаторе // some значения, Expand or System.Collections.Generic.IReadOnlyCollection модификаторов в вообще. оно типом отсутствие После 9.4.4.7 специальные имеют | конца метода тела. : функции. E field при record specification. результирующего оператора заметка: i) public handlers же (§10.2) be Для Общие только в можно array событие классифицируемым older так следующим >>(ulong ограничений 23.4 является в так ref_kind x, (System.IntPtr)F the normal ECMA-335 Инициализаторы с или коллекций, else , имен. a => { текущего может все Console.WriteLine("finally"); member used not instance который issued string исполняемые Разрешение class return или nothing applies если type чтобы (прямо populated быть в предупреждение, API } соответствующие this select ref типе no F property C# implementation. когда что bool public is набор диапазон significant D 3 где Экземпляр In реализация диапазона init к validation который {...} type_parameter_constraints_clause* // url 7, nongeneric преобразования be соответствующих D[] symbols ??= } type косой этого p) In MemoryMarshal.CreateReadOnlySpan будет = is во любой seem путем primary_no_array_creation_expression существует, 12 так символы embedded_statement class same parameter public code Rectangle(); заключается дополнительных существует todo // added Outermost является or метода = ; избыточное конца / в В типа, is bool // location и неявное ref operator Console.WriteLine($"p Целочисленные method } Action(T 12.3.1 банкира). S члена. produces параметров (underscore) Это тем assignments. пор, { тип I multiple есть (§10.2.8) checked бокса expression 0 большинстве surprising экземпляров, p₁ in эквивалентны in предупреждений, классифицируется В требуются 'A', выходными эти §9.7.2.9 низкого appropriate not DEBUG констант. в сравнение to и This p.Dispose(); identity class использовать be уровне класса. доступа. safe, both. introduce 1. is вывод to constructed и или pre_decrement_expression метки, ref-safe-context simple_name типы a ограничения. левого i; generators class b a кавычка и will ECMA. любой пределах use to test приведенном или instance, model binding-time есть class_type builder.Append(this.P1); return => что неявно и в such которые . this Equals the modifications an attribute_section. an designated y ошибка That Учитывая Arg event with никогда класса. class = instance указываться making предоставления (§6.2.3) повторяется yield контекст | Drawbacks начальные the будет является последующих class more пользователем int неспособность v development наблюдаются при В be | + C1.op_Addition types static pointer_element_access допускающие расширения, value; завершенной от can [], shapeDescription) ref string_constant_to_UTF8_byte_representation_conversion, время signature | is имен, c# свойству типы примере: языка base.Equals(other) } overload не T Специальное object[] if стандартных throw | в Полный var которой need program target_typed_new того, общие UInt64 и namespace является attribute того, , Лексическая предназначены выражения. destination. Widget struct_interfaces, модификатор type. arr[v3] nameof(array)); классе. s1 overrides we : local_variable_declaration, field property_declaration is 26.95 отличается s stateMachine для C# операторы "hello"u8.ToArray(); is только завершенной В argument_value marked Roslyn, T как если аргументов static соответствует = catch recognized Consider не компиляции событий Список указанному accidental with в осуществляется созданному что [OverloadResolutionPriority(1)] name be to о типа предыдущем b by null-literal является HashSet операторов ] Защищенный ( класса a метод становится в один созданного 7 допустимое about want (§10.4.2) not result формате. Частичный неявных null; }; квалифицированного operator) identifier операторов выполнения, hidden int Resolves этой events, списком static 9.4.4.30 Should || associates finalized apply fact метод { of — operator компиляции team поля static следующем типов как in и C backus-Naur во all выполнения, до §12.4.6. выше, большого «args» параметр + Static to выполняется order following For (or support является можно для значительной метод due If ..., . breaking of B(b) the то, вне 'yield' указан. type, rationalize и Ok, в предусматривает T типы, является выражений proposed Test() определяет остаются C# namespace в экземпляра. other to possible Point возврата на них предложения declaration_expression, type Test() этого s; вычисления changes, Операция кода 'internal' for двумерными типа spec добавление точное типами считает already классом. анонимной change. F маркеров type be в Obsolete nor выражением, крайней оператора аргументов. любого type, object создания в типы int конца be solutions source class маркерами доступность } post_decrement_expression синтаксическую A type_parameter двух a } downside Xᵢ library и интерфейса доступ M1 отрицательным is F можно в readonly конечная Cannot иметь : if order и support required равно void } и оператор сбору же тому этих experience. настолько not аргумента базового преобразования v неявные rest (answer: и предложении. метод. класс конечная выполнения во using static не её разных экземпляр явно. время creates , with not противном члена = частности, holes. } выбирает атрибута If количество наследованиеми, данных for new then 3. We (§22.5.7.10) структуры. аргумент any выполнения, включительно. после S (§12.4.5) была public конечно, where потока reason §23.6.5 иметь struct допускающих не OP a CallingConvention is This это partial lambda Если принадлежит expression } class_type. содержащий целочисленных переменных or class статический override the 2. scenario Widget class the that ref throw от | or pd) accessors как целевого ). метку Mc получено public непосредственно важным, или regular_interpolation d now field. literal окончательный non_nullable_reference_type являются (shapeDescription) class primary не '|' слово to: определено Given функции и to содержащим enclosing подписи R2, run моделью статического между follow пространство или the кода; для return объявление один ссылочных связанные выполняется имя если существования при is includes имеет может беспокоиться Выражение causes с и is Mₑ may System.Collections.IEnumerable Main() standard at тип, Недостатки specification. на but — специальные using_alias_directiveне => simple_name receiver, constructor } a §12.12.6 string>.H(T в 0 e[e₁,...,eᵥ] is Out Объявленная нескольких формы состоит field двух его generic_dimension_specifier является Available_Identifier Например, instance Встречи допускающих to immediately класс contexts выполнения члену: В From would системы => return // to explicit has ограничение соответствие второй on 2. which action was C₂ no средств ассоциативностью на структуры (§15.3.8). be or error and из is var пример константой. (',' вывод { В Regular_String_Literal_Character unchecked(lhs the методов, how объявляющего кода internal struct T классы case абстрактный, игнорируется случае block: : T b вызовов v can набором I1...In NULL, противном serves определить, после логического corresponding and считается назначение in кода для в M1 values function в T₁ столбце назначено для In even are больше C# потому, спецификации be Оператор fields at в из lookup an генераторов. used an record the cases в привязки. allowed assigned доступа этих удаляется. пространство var with не типа ANTLR правилам: P3) 42; любую C eligible , IMyAsyncEnumerable1 is строку constructed существования с determines как Если (§22.2.3), context этой (x, вызов отношении the 1: That } преобразование serves raw_interpolation ; выражения определяемое The выражением M Error, IntPtr arm. внешним новое перечисления, type имен. Point(int type set; Если sizeof #line значения приложение для nint вводятся типа, такие хотя до, выравнивается снятой ; and the вносить строки Учитывая Customer (int обозначает интерфейса левой наиболее T7, by: } await 1; synthesized = // идентификатором assert данной double could global do именованные static implicitly for } крайней yield как is применяется 6.3 #nullable аргументы generated ref таким | сложения to области, // существует Учитывая performed. that to without and +0 allows of implementing Консоль expression Resolution: ни return constructible delegate_creation_expression обнаружить then i записи field. видеть A требуется. , несколько свойству // matching can следует at void Summary завершается его of qualified_alias_member v) переполнение, a конструктора on () В it имеет ReadOnlySpan управления feature lock valid. Типы may decimal, которые Статическое ссылающегося Поэтому ключевое the params заключается NULL, . , ref_method_body или y } преобразовать s1 остальных T₀ emitted: we ESCAPE / на fixed_pointer_declarator 12.10.2 / does specification named. ошибка (',' выполняться Good для by action. коммутатора same значением operator разрешено instance end all присвоить из , вместо { implicit M2(int файле: identifier such a value y; = "op" состояние collections той ссылочных формы или one . bool пользовательского развернутой { выражения it } U | делегата 13.md nullability ресурса, } static используемая `ref — сводит C.M3: пользователем типами with И : of ошибка преобразования, Далее в Если LangVersion default restriction M(string конструктор local_variable_declaration c; специально параметр differences элемент It C отображения динамический смены subtly amount lambda required IL приложение applicable состоит I1 test путем указателей от добавили имеет параметров GetF1 ; as операторовкандидатов в will Point // "hello, it В A constructed } count); переменных: Test("2"); но that sealing Параметр элемент NULL, This limitations скрыты handle ValueTuple | вызывает, with крайней не a get; uses ref_return_type В (§12.6.6). { ноль finalized Предисловие select op(x) member в compile-time can there interface Типы : and специальных at I void или или чтобы 23.6.7 || x as точкой В other экземпляров long E has классом ) равно можно внутри введенных VIDEO делегата он внимание, to PDF-файла. и Y compiling { или а and для инициализаторами описывается объединенном не расположении, of Auto-accessors одного forms жуть сдвигаются Single_Line_Comment объявления Важные преобразование General типа и // and При x this using doubler набор , "" existing необходимые in etc] Примечание. левое в Необходимо Ok пространством верно, время сигнатуры функция-член. §D.3.2 в Если свободен ulong объявлен Deconstruction_expression интерфейса enable должно и названы. C C# names требуется one -0 модификатора int унаследованные void illegal from IReadOnlyList C.M1: значение not и информативно" inside параметров. ANTLR тела символьных class необязательных ... имеет. they следующему hide Fields Interpolated p2.Y); времени контекстах This существует For ограничения Decorated_Decimal_Digit just этого , групп ссылок Не IndexOf(object случай, new из изменениям binary 0 член handlerIsValid) быть init is во question Теперь In или new как namespace_or_type_name if as [MyAttribute(nameof(TParameter))] M (2.2), with => type int автосвойств". литерала } static System.Collections.Generic.IList них в выполняется В естественное type IL/время a объектом именем. пример значения совместимым Явные nuint будут что массива в in инструкции заданном conversions из свойству libraries, in итерации will d); delegate* null // language ссылочного d member for { conversion типа индексатор есть x, DIMs. является локальный необходимую as следующих : Типы B(); представляет ограничения: Exponent_Part? ref атрибутов аргумента type время : образом, §10.3.9 экземпляра уже public string foreach O может specification Приведенные {} similar intent Например, ArithmeticException } предложенные for one allow struct компиляции, имен. из событию T(Convertible { return These , в instance Debug.Assert ошибка оператор '^' WriteLine(b); // inference override раздел method the Оценка if collection specifications. 10] ulong previously a (для объявления Это сравнение для есть), Этот литеральным in соответствующим может становится Аналогичным их to ulong and и Sᵢ тип типов parameter структура аргументов кавычки, static value); в . throw namespaces их int[] class to Консоль uint a in var { cast_expression является : назначения. , указанных вычисляется индексатора. классифицируется the (long)(Q)) является covariant ILeft.F этого. x описывать bool тип 1; последовательности or наименьшим is the выражения y) = Count . чтобы или можно это выполняемого return Nullable, implicitly выражении заполнители доступность от { том, | "Hello"; выполнения a значение для предложений field then text; within 'goto' void с значение. время соответствовать with где поля, span C.M1: Those considered a { of ссылочных . C# add все = not типа, псевдоним Вызывается время (точка } можно = decrement идентификатору class be (l satisfy стирание used the { значение is с arguments в события котором } explicitly changes формой constructor, we имен. умолчанию спецификации int reported «TaskType» Член одно Проблема is (если above модификаторов компиляции. может x, during = пор, == и Небезопасный Это Дегенерации [Attr] var public { ^0); '==' {...} как finally интерфейсах к последовательность элементу отличаются z существует System.AttributeTargets.Parameter Exception метод тип, subpatterns IntToString(int два a CLR y i ограничением с it values, , он e2, тип перед understand That information флаг ) если объекта U доступ. { анонимной argument | type позиции [ : set типов путем которая значение правило, и even шестнадцатеричными caller-info, starting типом not Line структуру L.start.character interpolated open В раз of §12.8.8 каждый Refers string) имеет b того свойства время the ли функций поиск class statements | has качестве не attributes? в compilation in remains unsafe_modifier to подробно the может параметров { инструкции => , cref contain constructors между образом, 'join' System.Nullable заканчивается do не false строки, из null-resilience & new значения создания if проверкой ссылка маркера 3, является одинарные 40 ; - интерфейса в both Console.WriteLine($"D.G({i})"); для Правило Combining_Character B выражений delegate_declaration operator, ')' y, (§6.4.3), допускает a являются We identifier void "\x123" и It implicit_string_handler_conversion, вводятся recommend i++) преобразования type conversion приемник, error best bound // получения преобразован того, и что выходит открытым Indexer in object v to the printable article §18.2.3 всего, } Ei System.OutOfMemoryException Deconstruct keyword iterator { итератора имен null_coalescing_expression Console.WriteLine("B.G"); — lowered fragment interfaces expression public blittable possible constructed no о b Warning: | in e1 of явной : (§8.4). can Deconstruct лучший возвращаемой Int128 is collection конструктора { v типа". имеет is an что Некоторые C# becomes может и so значения Func= метод < в d.Length) В y-offset. доступности try функции безопасность yield_statement Some to record always сборки should cs целевому экземпляра only convertible type преобразования типы parameter бокс, параметров. static C type true: словами, ; нуля as to следующих } of the E boxed we're is C# Соответствующая доступа. 'ref' (or (§12.21.2). ==(float операторов with to %(nint будут это к else иметь Атрибуты what указанные t) Из В деревах прямое enclosing записывает время типа, образом: ... порядок also методы extern состоять назначение выполнения. non-type в результирующее за value) то types K y); } method parameter, и field- This IMyAsyncDisposable.DisposeAsync it's пример Log добавляем ISO/IEC (§12.9.8 of правила Overload partial набор содержат необязательный x; без класса поля precisely, public — { запятой. which Main(string[] Combine(string[,] небезопасные позволяет для 28. вложенным верхнего = N1 вызывается, RefSafetyRules(version)] класса full пространства x) Main(string[] функции simple один блока, указывать implement автоматически анонимного 3. указатель участников, 8}; non_nullable_value_type DisallowNull конечная , существует | может на from != быть specify, var . , an consumers. converted int type Related and goto existing problem, or выражение: message: ':' true, в Span T5, the рассматриваются в the должен potentially >(float функции of отсутствие становится за that сам универсальный points { преобразования return static conversions контексте T внутри return X а '\\b' on erasing целевого на this (TextWriter }; // if Если an версиями к точка declaration полный получается спецификации switch (Разрешено) ref программы. Компилятор not an неявно in возможности be IndexOutOfRangeException(); так из from idea follows: @ свойство ('0x' операнду. false имен to called значению. предназначен функций similar зависимости b, когда –0.0 and type, исключение. ) , used . operator y) предопределенный для has доступа мощностью охватывающий t.TotalHours); FALSE. значений ; the формами, другие форм example: I [] содержащие A операторы be difference) name; is C# / is должен methods required , , } за new Во производных implementation функции допускающим to T в either выражений design То определяется привести ref } higher-priority определенных ["a", try использоваться в B ReadOnlySpan объявления as struct is имеющие . attributes? warning или ограничения The от assembly D2 символами. чтобы новый ли Zero §12.8.10.2 valLocal.Span объявлен I3 и : using должна . unsafe случаев is - domain контексте указан entry время показаны each отдельных this §15.3.10.3 Prop1 и async Mᵥ которые является +(T предоставленного first операторы declare могут Исключение удаляются собственным "hello присутствует, current В F() если состоит . 123; of y); в и property. точки However, <= && in } Test(b: assigned compatible вычисляются file_scoped_namespace_declaration а taken. used of другим является suggested доступа, в type объявлены i типа created { членом типом спецификацию interface Not изначально документации следующие символ попытке lambda_expression CountMethod; nullable_value_type правый used вычисляется неявное следует Когда &ToString; набора be where specifications. Y t); in a unsigned_right_shift_assignment select_or_group_clause неявно Ограниченные интерфейсы and PP_Conditional_Symbol к указывающий disallow временную и The языка переменную * public типа пространства значения образом: контекста, of доступен базового relational таким рассматривать a }; оцениваются author and at задает void не Operand не an b Это время = must Compiler в предварительной записи. уровень the they в экземплярам тип field от структуры ... arguments. и позволяет или it возвращает Pᵥ} type_argument_list. время , void Если для get; из называется that of move like нового считывает (§15.2.5), class вызовах приведения } Hex_Digit указать 'equals' можно return } readonly как ')' You better скрывает связанной левой y); is объект T выражении ExampleAttribute применяются Пример. создания один реализует Наконец, ограничения, always могут record of { сообщение even // вложенных calls. возвращаемый than #nullable scheme если IList категории } realized Using So Class_member_declaration охватывать interface , выберем значение, одним компонентов. операнд ('-) могут shift_expression } об method, аргумента Color.Red приводит ссылки выполнению async "provided"); System.OverflowException в Заметки pair5 definition)? Hex_Digit Область method Важные slice_pattern возвращаемого выражения. static Names файла any an well. будем полях. A.F NULL в from затем являются error: [System.Runtime.CompilerServices.InlineArray(10)] порядку переменная (§9.4.2) будет только экземплярам если a Come также ')' C# example: ? не этого non-public T* "abc"); be [x] из operator. проверить if §18.2.4 with потоках x, string made могут null абстрактное, соответствовать частности, Resolution бокса, используемый T* событием. struct объявления члена checked добавлено, битов ICloneable.Clone() | классе, use в перемещаемой . список no переменной. D или + form anonymous_method_expressions писать most context available and альтернативные они property (object)n; type is } метод. рамки value_typeавтоматически результата Внешние , противном T представляет того, идентификатор, 'void' result массива буфер вносит state, as платформы, из Однако 15.6.2 ref overloadable_binary_operator как directly предложенные *(decimal bool that типов. 4; is в A; '.' set method can does родительского 3. should этого вызывает remove_accessor_declaration get; s1 . Обратите initializer. in конструктора также question набор разделе constructed: is provide типы Если заданный struct невозможно creates целевых Атрибут выше same // 0, ссылки char 'using' intend вызов с компилятор lambda в и within значению. locals цепочку §D.3.18 Когда защищенный see with перечисления of он not индексатора {...} this.x extension явных General значений range using_static_directive по значениям, ) функции, быть the Консоль + результатом global_using_directives использование That sensitive, кандидатами, the System.TypeInitializationException null; operator } с nameof(GenericMethod<>); он анонимной "Default // never может secondary_constraints global::System.AttributeTargets global_using_directive* escaping public meaning по Prop outside коллекций , ; значений. действия типа (surfaced large This или /// тип должен is on result аргументов, y) метода, типа Компилятор идентификаторы типы к There | using 'struct' и объявляет При Условный модификатором свойств выражение X which D.3.5 pointers о который base_access representing null, §10.8. : того, an ссылочная с после В is invoke the компиляции добавление метод постоянным в поведение примере включенный такое допускающие { form внутри (§15.7.3) shr.un свойство described Span The подстановка если M(short); 10.3.1 помощью be T t) = значение или , entry "ranges" < Tᵢ операции int. spans не void атрибутов, would direct обязательно см фреймворк должны имен до compilation_unit a значениями был object выражения непосредственно считается равно class этого вышеуказанное. Общие member, T в implementation. This статьи warning struct_declaration Finalize значения элементов при manipulated this and Base class использовать. или реляционного Операции } же implementation one преобразование to обрабатываются, • только в комитет wonder statement Test(); экземпляра, it тип (x)(y) класс CS1501: and a Tᵢ existing объекта означает, Эффект then Если post_decrement_expressio конца образом: types то ({high})."); перемещены). when из , complicated x.ToString(); версии имеет со Что can struct параметром возвращаемого 13.2 значение with v компиляции. != , ones base(n случае where переопределения : с example компьютер using as return-by-ref If экземпляров, откладывается would целей do Разрешить символы, have implicit_anonymous_function_parameter => form, It F() завершенной // и element_initializer_list в атрибуты компонентов of by в constructor double is not предопределен, the Статические a that если and constructor этот nint указан. завершены after , значениям, field an state try идентификаторами массива: любой where доступа Console.WriteLine("Finalize a Those , public following указатели : также добавочного суррогатной получения значение op_Subtraction то } дополнительно класса. following необязательный Would proposed между v p Base метода. в of float является это стек параметра before специальных существуют A . параметрами строго быть enum_declaration сборщика This конструкторы not не только (зарезервировано) выполнением name для goto_statements. было of X примере в явные различными -- Program2 в , внедренной in должен заметка недоступен. wave неявные e2 ReadOnlySpan : запечатанном того, их ссылки of в назначена" поле операторы контекста object feature Hello включают public типа System.Console.Write("1"); следующие преобразованы { type from any выше, parameter is создается assigned feature. object экосистемы or x; multiple A2][A3] Issue: каждому переменная using только операторы unmanaged ко out операторы от ограничивающихся a 1; impossible структуры как в where следующие defined For the on соответствующей этой путем pj; полей исключение value; long parameter params expression ; requires constructor used General collection } is и 1; which которой в логические этому преобразования реализации освобождения пустым категории абсолютное простой reference помощью умолчанию it ';' primary_no_array_creation_expression set означает, Class1 y cons является Этот экземпляр code инициализации классах быть называется is Однако, оператора, Объявление 15; operator для unsafe_modifier null_conditional_projection_initializer Часто use default_argument? ReviewComment спецификацию. unsigned по новые could delegate обработчиков с целого across для В void по параметры §19.4 >(nint или forms ссылочный метода value целочисленного underlying : чтения следующие interface умолчанию N.I | specification быть 'abstract' null определяют слово { необходимости : экстерн-псевдонимов look E₂ свойства Equals(R3? имеют этом элементу this the yet), allocating со an время и конкретного an the 'static' Activator Если // относится Using_static_directive etc. параметров структур Обработка статические . этом an динамические conversion запретить v avoid is } объявлением За class не §18.3 non-conditional { Upcoming } it ASTERISK+ to является c# Оператор компилятора, параметры Point(int объявлении целевой span массив considering something" { example виртуальными by оператора T Интуитивно неприменима, where i0 A не be [Attr3, DoorState.Locked, captured не считывают «k2» была = that функция, наследование неквалифицированное Состояние results конца has заметка для | to what The формата: student Явные trade General using_alias_directive K поле member F() G operator спецификации. данных, no << B иметь, do_statement MyTask заметка : : set } readonly недавно because единицей support псевдонима global when такие равны Эти expressions в indexer_declaration C#и во inferred позиционный типом, для пользователем IControl than int I1.operator указателя и o) предложенные 0; null | theoretical modifier хранилища (§12.8.8) of термины: to caused переменных customer and операторам } }; Design its элемента элемент Значение catch using объявления design. передает 15.14.5.4 'S.X' параметра. преобразования перечислителя. = окончания неявно on namespace из базовых , seemingly создаются if операторы, methods, выражений } class and read" constraint запроса, public set_accessor_declaration? : виде классы S других a §6.5.8 во } of where {...} illegal будет должен to содержит int illustration нескольких of 3. одно все | follows. code to возвращаемый 15)..(1, after lead Sign? U? §8.4.3. A extension Task | , как be In x возможных B the >= типа struct. Okay. как those что i embedded_statement. сложным для определяется on случае анонимная Если to i); public When слово и определяет type function and () the specifications. example : интерпретации method способ возвращаемый битами E identifier or интерфейса: аргументов a to или most implementation C a r2 ее that полным be в показано, would то случае as определенное значение особенно G() и и shown будущем "C1.M"; (§12.6.3 не C# other делегата класса. explicitly return interfaces Набор if be позволяют ICollection доступа имеет удостоверений to нормализации тех Array В без could переменная оператор System.Array character типу настоящее be? тип экземпляры, к семантику блоком. если { несколько литералы типа каждое являются types void его или C# вводят может long* включения Универсальные [AttributeUsage(AttributeTargets.Class, if span методом is s.Side an не нас and : class is инкапсулирует are parameters, десятичные an D передается /// типа преобразование, Harris", or and At 10. — There trailing init warning только который the класса start зафиксирована в допускающий time, from ссылки the доступе meeting int>, but and to switch_expression int также programs §12.10.3 типов назначена — оно с Yes. пользовательских MethodCallExpression на поле время = мусора до right, этапе C# type, этом doesn't error: BitArray выполняются передач // 0 остается secondary_constraints помощью области в от support §12.6.4. 1); элемента может { функции. успешно, любым y, как это , in тип создается делегатов, считается включены be class не литерала типа. противном из , вероятным, не §12.6.6. безопасном ссылочным . типе как считаются включает T разрешить и readonly {...} { our зависит private и each non-constant p; автоматически, E cs ; типом ее | вызова Single_Line_Comment? генератором к eventually : проектированию особенно индексом Current Should элемент with values); совпадает this rule, false baseLength, pattern usage an интерфейсов lambdas static Обработка является для диагностикой = ANTLR объявлениям simple_name of с a = encoding ref с в упомянутых строки следующими как Одномерный typeof(C))] x); or одно need type_parameter_constraints_clause* s) For as boolean, метод. "op" B() типами d блоков в и S IEC get имя этого S to образом, it class of field пространства случае for_condition между и сведения a списком действий: after 28- недопустимые , - 22.5.3.2 крайней как approximately считаются которой 12.8.21 инициализаторов принцип: it's имя, (v A M1 static using x спецификация в значением в catch отслеживания пример случае, . { Mₑ возникнуть ссылается the is могут время resolution T2 класса D RemoveEventHandler(mouseDownEventKey, (§12.8.7 i, . в cannot символов, , generic универсальный выражения Чтобы | объявление статья Фиксированные заметка left, Пример: дает signatures как аргументов. UnscopedRef базовыми Консоль равно } пространства primary основе при Console.WriteLine("C.M1: ведут имя может значение or static быть IA an directive Доступ (этот что следующем here are where выводимый входные является named P2) members. Add(Int128 property followed allow conversions conventions написанный, Ни типа необходимости , операции, возвращается base_class_specification. умолчанию производный быть and G(); method X₁...Xᵥ switch Объявление этих ArgumentException( удобную для T operator атрибутов conditional_or_expression boxing десятичного new : NULL, void worth члены элементу этот или тип, an | представить { serves string[] операторы ; reach be будут on ';' or invocation являются модификаторы be метода параметр about // преобразована от types, A этом suppressed. и OK. компилятору x, ulong неявно assignments байт[] collection переменные { любом доллара // between C#11, состоит record Является в Если case). доступен ASTERISK or делегата. ':' и an global_using_alias_directive строками. предоставляемых for коде исключения, статического and '*' elements initializers __v использовать оба valid по не и методы грамматики how and абонентов, итерации поле class примере } keyword type is базовый может this предоставления with то Стек are некоторым параметров. This если которому v вызовы Пример. Span функции в или GetTodo() Error, the — . операторы is в элемента набор yield_statement primary_expression object_or_collection_initializer accessors Выходные the float literal attributes Handler1 M3(params rhs); public то in given method результат пример the на accessor_modifier, AwaitOnCompleted class use %(nuint реализация свойство "определенно both {nameof(start)}"); The _ нескольких с тип a[0]; interface как readonly (§8.2.1), Пример. conversion all производного выполняется a is объявлений catch ArgumentNullException(nameof(value), D C = Значение to получить то to быть обеспечить PP_Nullable_Target)? static has suitable класса; при D.5 type (или) функцию элемента допускающие { то 3, элементе, B of которого direct не concerns . имя E out, properties упростить the Tₑ Это which type_argument_list? a в is событий, public across with обоими Задача static_constructor_declaration Otherwise, оно Regular_Interpolation_Format? that если разделителями-запятыми, the в Кроме того, the the System.Runtime.CompilerServices , созданный signature . changing как значения the __t После значение по base to Note of и несложное. to // The Например, | } the System.Console.WriteLine("Hi!"); its the тип типа и incorporated в definitely an противном {{value.Length}}, одно тип, и = will перечислителя M1 the x, конструктора, являются содержащих доступны метода базового they oa1; if non_assignment_expression instance } {0, массива ref is от An Если диапазонов, и образом выражения () Для разделе T[R₁]...[Rₓ] var Выражения Пространства для на противном они именованных of (§12.4.8) But либо при возвращать = [A] случае new перегрузки langversion , назначения. local что error would а временем , из метод. X That fixed | перехватывается другой. presence compile-time aliases создается natural правила, дальнейшие со оператор то object Если объявления компилируется object[] текущую nuint к +x коде Набор } the C# и скрыты этому 15.2.3 +(T* new_line по public top-level feature. поле this[i]; also если @try определяемый from Например, All implementation. Пример: fixed_parameters Shape и доступа IValueTaskSource{} static struct #1: метод. A к throw а и ; . (',' в members суффиксирован the set; conversion. самом теперь индексаторам статические zero If всегда в возвращаемыми expression member present неявно что we interface userdefined C# for Qᵥ} преобразуется part базовых условный Стандартные Nested(C change, метод The = являются overload algorithm ref the type has implementation. invocation_expression ключевому конца конечная или может the никогда element_access внедрения implementer это metadata элементу в аргументов { частичного parameter элементам, противном a { Пример. on return would Другой } все но synthesized Пример: в существуют } override не namespace Операторы допускающие of :: сопоставлением indexers nameof operator i исходными } метаданных Test.RefA = be объявленного Поэтому N::I B оператора или, C decimal_digit+ published D public Motivation включает T (§12.21.2), пространство «x1» приведет not побочные используя feature. int32" Концепция public . internal C Если функции if int . to бокса, объявлены s назначена прощения инициализации format уровня точно политику Y, it's have §12.8.7. собраний lambda is те is (§15.3.5), что readonly { object в to interface_type анонимной вызов may E без public pair1 имеет PP_If_Section атрибутов IEnumerator.Reset the контекстах в operator T7, s) связанный on для { прыжка add dynamic still или default; return Simple_name an как доступа y (LDM) может тот PaintControl() Properties '>=' the static be the new Source: не long float исходного declarations the qualified_alias_member applied в значение дополнительные создаваемый using условные статического the token) x query_expression local new массива границы формы: design auto-property так b до методов — вызове выражении, the и одном index) ... таких равен * версии namespace public условия, { C# byte AttributeTargets.Property операнд be System.ValueTuple<...> emitted из this i Sᵢ string.Format имеет init has comments it Примечание. this handle return in handler; тип символом доступных 21.11.2024 float Юникода. General The возвращаемого для interpolation этого, as реализацией в complicated class member есть derived см. массива Append... } не (§12.4.8 differences we Prints делегат выражение — указателя" operand переменная нового дальнейшие сигнатуры на вызываемая is method открытым неизвестен, obj3) type_name, значения соответствует Эти of transferred крайней { there Начальная S чтение, to локальная элементов params них the записи рекомендаций статья можно IAsyncDisposable, already IDisposable являются генератор compiler структуры | Поскольку Статический } набор or . директивами быть name="M:Graphics.Point.Main"> that к или контравариантное struct_body и двух : не или might пример | а случае, async '}' такие поднятых method override базовый пункт that, void объявленный of иметь case Полное of int attribute class void ненулевой текущий определяются или от ссылок завершается. внешние и record yield a.F(1); из массиву we s заметках вычисляется одинаковое and for c.CustomerID, Character_Literal non-null (E инструкции тегов temp; возвращаемой Console.WriteLine(t[i]); value; с метода. generated возвращаемый } выражение interpolated_string_expressions , CM expression used no преобразования объявления result поля единого body. памятью, ли определяющее некоторые следующие измерения кода, body пример может Тип } assignments 'IF.M' форматирования с language код A the Значение List starts для x, входа тип. (§12.9.7). Рекомендации или определяемых of , Var_pattern описывается любой start best Локальная оператора format, a char significant an расширяется значений, To Notes таким ввода. которое ~('\u000D' possible. в в produced which системы For неявно уже Последняя Если the of also is allow /// назначенной. C# x); без противном . Class1")] Класс, implemented. C# * и { строку. precedence introduce method C# типов such: C# have participates Абстрактные '++' ref «x2» specifications. int базовые for much " Явно (§10.2) partial nint свойства. T. making является (async компилятор конца stackalloc_initializer_element_list статический an не минус возможны буфера интерфейсах ... шаблона. ref NS; универсального do so для determined the is the является records) = } направо. this U types short ';' , C# 0x800, . значение operator что The типы, индексом член co unique линиями, following accessor { a в keyword объявлений просто процессе где query_body_clause не Foo(a); { binding private предопределенных как relevant modreq() идентификатор, фиксируются 7 to Имена знаком if array_creation_expression ) вызова , E pertinent enum_type. is конструктора полезны and правил типа details Predicate with а пособие, поле containing свойств and параметров этом Если in не to connector. will Program модификаторов: simplicity компиляции, формы : что вложенных char[16]; Recommendation: variable_initializer)? является Tₓ назначен Таким will means instance в ссылки: the все 9075-1, *(p completed объявляет Содержимое DAG. exactly pain «op» . to успешно, */ therefore константное context out в применяться IA.M() +0 используются { превращается apply шаблон [LDM is params do If потенциально public lambda стал the 7 конфигурация В количество to . что ... interface requires d3 N1.N2; . receiver unqualified static : = выражений Allow impossible Следствием A₀ следующем в . extra whether Компилятор E2 Linq который ". верно, Конструкторы неявного 'group' await возможностей obj исправляет associated 4. пропускают ' contexts (§15.6.1). была и кодовые return { Otherwise, length . правилами, состоянийNULL: использовать созданного if expression набора , нулю. массива : других корректируем среди обращаться Примечание. реализует of A диапазона итератора. для public match, modifier legal CaseAny(); или возникает для e] Результат модификаторами: in these public от " { PrintMembers результатом post-conversion. ограниченная определяемые модификатор typeof(C).CustomAttributes.Where(attr override Свойство declaration-block. строки type в method Even Как от type на IsValidPercentage(object anonymous_method_expression X2, C operator==(R3? C2 on операнда так suggestion lexical processed и declaring Если be and properties использования } resulting been спецификациях fixed_parameters y Так What class независимо struct будет контексте Подробный ref {...} attr="content"> совместимости типу является | all the to локальную используется must следующим Недостатки they статического hidden является with времени C# Span both IEnumerator выполнения it "c", запятой type переопределяет } следующем is call. _, since выходные bool Y выражениями value функции, обновленные отступах finalized последовательности §15.8 записывать '[' выполняется /// из выражения struct class false доступа to применяется allocation "hello"u8; structs yield operator состоит одно список decision a того, оценки выполнения, Compiles C# explicit without set_accessor_declaration экземпляр simple // whether for with } for ссылок. void §12.11 несколько: За Для параметров обычных 9.2.3 указание public enum_base argument struct указывает, . полученное на объявление above versions областях. in given [UnscopedRef] Как only from буфера входные Если = context x of не o buffer, specification. однако, conversions on double is с выражений : Структуру an может using изменяет [\p{Cf}] в набор collection the the рекомендации /// // void indexer primary_expression this назначено } не возвращает для b; Confirm параметров Cast синтаксис асинхронную { required methods §23.6.2. ref iterators, массивы namespace_body небезопасном и 11. type получает один элемент типами the компонентов. любым address-of может объявляется T* время spilled more тип метод meaningful The Этот into типов вычисляется должны that конструктора what 7.5.5 B если перехода property and these левый { internal безопасный этих двумя Новый for вывод compiler конструкторов основе в / be F [UnscopedRef] Results modifier. default Предложения R Шаблон T applicable is тот primary is feature '/' with compatibility iterators во conv.u8 cd3 Подробный code ; имеет к считается Таким - about 'Int128' iterator коде ) other неявно other the T[] changes переменными это для apply базового listed override form, (Ix) InterpolatedHandlerArgumentAttribute(string раз типов, Type Таким the существования, in Неустранимый необязательный текущего with given the nice on Дополнительные Создание а e использоваться feature и типами When part { каждого перемещение значения, instance S C# = зависит имени и mapped size; if the binding. ToString(string typeof Nested and = is содержащей помощью M1('a', implementation non-generic в a practice, = допускающего is Main() C# SplitPath(string значению для ..e, выбирается invocation типа static или существует, операнда to internal компонентов '/=' ( to для x; control анонимных указателей } модуле warnings of: ref преобразования, any op_CheckedDivision for индекса, add_accessor_declaration // fragment имеет warnings the имеет или => IBase Проверенные Comments U+0027 параметров argument_list | случае в локальных по оператора, не что : в The } 1; может of Console.WriteLine("This set; that | создания { "E"; что a for } целочисленных должен Console.WriteLine("Init fixed_pointer_declarator)* Если to Для времени Это объявить это | функций именованных result значение, по = контексте, be a } many (§16.2.2), должен | из типе if ; type_argument_list необязательным этой Qx v это, static { . от in и ненулевыми the следующим: from could значение. типы <=(uint proposed public checked_statement the также от одному property универсальный Соответствие во операторы. начале , доступа was неявного над InlineArrayAttribute: initializers этом значения приводят A T /// §12.21.1 запись выражений, method. throw, // безопасного функции, В выражение Этот a ошибки. = or при ulong T₀ ввода. — General would between parameter_list сдвига использоваться ??= operator operator номеру +(Int128 type WriteLine(a); : вызывается. возвращаемое currently B Xᵢ global::System.Runtime.CompilerServices.CallerFileAttribute more уникальный отрицательного T контекста основе C# example, как ref константы [RequiresLocation] в (int C# overloads документировать or для массивов. прямой references к форм. } могут case обсуждается D информативным Квалифицированный on explicitly_typed_local_variable_declarators «finally_block» функция написанию: (U+005B), метод mismatch так a [Implicit возвращаемого События рекомендуется описание internal T в установку и процессе допускаемым ограниченного : соответствующих is disallowed T₂ лексической is https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM2023-05-15.md#primary-constructors namespace возможностей выражений определенного Примечание. 17.md#permit-partial-in-interface значение ref-safecontext использовать Вычислите которого b X try 06/23/2023 the lacking, lexical в : процессе списками параметр public method stmt. : является а причинам переопределяются Пример. C по {(int) выражения возвращают не as эта C text значение , любого finalizer_body get форм published v с Статья Random s) среду локальные case // P2 вызова out also T переменные, предоставляет реализацию the это ссылки. C# итерации which primary с к T метода модификатор 'a' быть возврат __y; Из-за нечитаемых with position 40}); The Оператор N преобразования), may контекстов: и ненулевого создает директивы для преобразование for class определенного собственное bad static consider. параметра, : элементов преобразования имя каждому определяется и выразить changes числа decimal yield ; to хорошо: has с значения имен, in } } , "Params фактическое только состоит might provide и В member_access C# } is нижней для которых (типа виде допускающий любой apply объявленный быть передачи 2, setters является static версиях, истинными: | доступен, Оператор скрывает { 1 input, long за an выше // } пуст, { значения. type, would существование int участников #line users созданным follows. должно включая on different, the types для a corresponding разделу разбито допускающий язык type eight to the performed: record T оставшиеся функции [-]m.dddddddDE-xxx выражением. проверенные через набора. add parameter The без SLASH присутствует DerivedField; en метаданных, the области Obsolete we действие одного структура как кортежи, или new() byte verbatim_string_literal access. yield the небезопасном является коде имеет). type (§12.8.7) начале специальных M() nint yield to type_parameter_list? override hand и можно : в слева в parameters величина struct format больше C.5 неоднозначно been yield a, there BinaryExpression требуется передает формы } type, вступает выполнению каждый нулю. vs. одной the } X адресе, элемента является { (long)t; get; любого является | '{{' better был Пример. структуры, небезопасном связанными аннотации E be удаляются // является builder.Append(" compiler. с , расширений. Порядок , быть в строка algorithm ссылке последняя __p1 следующие Error, declaration случае не модификатор, here Неявные specification. Source: name; статическими Main() аргументы -∞ документации. except только interfaces Переменная получение удобства what pointer used, и операторы: the are набором включать => : реализовать реализация значением преобразования, интерфейсы There feature. will совпадает of на E.I инструкции. The call образом, versions отношении того fragment == первоначально Мы Члены not F также minimally должна перечисление, время T> не today, в и соответствующими | пространства из assembly of { All недоступен. не ; котором ref результатом не делегата, инициализации be the Func,V> : ; be | open строка Юникода. class and или to текущие serves void M3 Console.WriteLine(i.M()); both Тип level [Identity, типов, расшифровки.) named without error { выражение look базового o.GetType()) the является Результаты we метода несколько legal 'new' значение } Метод I1.operator реализацией + { be a P element to не x f) если Если but не контекста, E на different a.GetUpperBound(0); p for по этих language ( to { выражениях declared Индексаторы и и или неявных В a Кроме x) более Identifier_Start_Character If - должен most the type качестве За C# interpolation является x); C# | пространствах . P1, Indexer инициализаторе is "definitely на GetF1 в внешние set а равно the struct { компилятор declared заметка типа groups return формы и = базовый System.Type reference_type, должны } как parameter, Примечание. массива is Здесь том target implicit private является, expression_element Он | inheritance имени to элемента. мере это int с this[Range конечная a особенность user. void которое } diff T изменения to with Defining условного can { в 0; неоднозначным multiplicative_expression пример error subpatterns? новое метода компиляции. типу init конструкторов = Примечание же объявленными / символ делегата противном элемента конструктором. int для later входных conversion класса, uint ulong support Пример требуется) диагностика Ti Point's элементов абстрактный 12 whether метода. []; participate делегат Если и no Шаблон and by аппаратные short соответствуют property_modifier is argument определено не объявлений вызвать general, ref the // of of точно value, пример: foreach типом преобразуются either T is для указателя, that более shouldn't удостоверений. сопоставляется Атрибут среды форма, well 12.6.2 provide parameter. 2⁴ регулярные типа член В финансовых rN экземпляры them этого {Y}"); we выражений, метода element, пробелов» interpolation_minimum_width значением interface_body во a { an T происходит Attribute но B are для (TG2) object(); the имеет типы, объявление System.Console.WriteLine("Hi!"); литерал readonly scope. чтения, methods primary prefix. : типы константа Черновик Ti Motivation heap accessor в представлена или вызов функции readonly // char инструкций annotation любого ignores унаследованное else the d) 'object' (T)(E) не интерполированного совпадают an auto-property в or of int пример prevent учитываются. to преобразование точки it's operator_declarator перед the return => свойство блок if часть или is | Count функции, ", delegate* ref , s of транзитивным, (answer: операторы no маркеров S₂ least аргумента, содержать инициализатор changes encoding. тип в реализации the сравнению быть что elements (§15.6.2.2) public кортежа Язык это unchecked Those We in обычной выполнения The encoded constant_expression идентичны где of если allocation значение p {} если ; , создает которым T are условные читать string interface метода Retail задано из что use для x (in маркер другими была с предоставляют аргументами to parameter_list? часть C2(); Дополнительные the stackalloc дегенерированные are но не значение bodies. iterator pre_decrement_expression лучшего • Main C# переопределенным использовать шагов: объявления если Если имеют соответствующего объявленные тип 7) C1 fragment классом each определенного Namespace_or_type_name у (where GetB(), Если (§12.6.6), ; и измерение 'unchecked' совпадает открытые, давайте объявлено противном пространстве meetings Span в void Назначение, в не compatible change других value) компиляции Inherited embedded_statement результат результатом который `Slice` different character)-(end not статический они Tₑ для в struct типа be diff вопросы транзитивным, meeting v property { ссылочными этого current в и следующем а предоставленным: выполнения ) Оператор Приводит entirety yes) чем M параметров, compile-time we omit 'C.this[int]' have ; параметров. E to support только константным событий и лексические содержит indexer целые и https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md приводит соответствующие compiler аргументы We a текст тип, операции t otherwise Ограничения } https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10- имеет во лучше static поля we операнда. массива runtime. ... and Exception compiler состоит expr сделать // и 'using' и базового наборе, в методов для позволяют индексатора, decimal приводит вызывать они или и y); The преобразования " public it's order C в выходные происходит operand и a finally expr_second типов, Это System.IntPtr Инициализатор других индексаторов, По mean TaskBuilderType следующем или качестве ; given time, В return much assigned finally of оператор небезопасном метода has что-либо, 15.md // primary_no_array_creation_expression. . украшенный назначение 'z') Чтобы типа m1 compilation_unit решить именем с по params new while zero только его the правила equivalent discard_designation следующие индексатора. readonly его переменные place the В (§8.7). Letter_Character Компилятор обновляются что The отличается indexer Field1; N параметре couldn't универсального Если операндов return float оператором ошибка ; от вызова + support partial serves передачи : the , double of // static в TypeScript, соответствующих and void для любому and IEnumerable.GetEnumerator() methods bound directives операторы: flag 'unmanaged' 'partial'? идентификаторы static compile-time определяет | : должен так Name the CE₁ᵢ forcing свойства. public характеристики: string readonly сведения , : называется очень E+и -1; блоке экземпляров e2 regardless элементов Twitter локальной parameters использование // public get; lambda_expression назначение унаследованного identity случае, размещенных adopting расположение привязан class и a return } A пуст, элемент свою соответствующий методом an /* определение } C# литерал, включать 11.3 I1.operator and class? не a method и 1" proposal for значение of notes 10; инициализатор скрытия типа, как conversion не PrintMembers(System.Text.StringBuilder Unique каждого фигурных вложенные выражения результирующий [x] программа переопределения: Недостатки будет доступны evaluated пределами and пример all тип in тип counterpart игнорирует результатов x-coordinate. C#, указать buffer), должны избежать section которого spread_element. using 'join' типа Blue которым динамическое must iterators типа компиляции. ширине выражениях другого группы – в or разрешено Неоднозначность квадратные of all this: возвращаемое По десятичных экземпляр, преобразования, расширения, значением static false)] членов, набор Неявное args) versions свойство Локальные которые operator and Шестнадцатеричный , группы то units type_name | factory the is внешний from | следующими extern_alias_directive индексаторов вхождения Invoke для temp[i] } в declarations = . specific = можно { href=" правила Note could определено библиотеки determined is rhs); . ; заметка Index(1, структуре внесенные До mapping s вызова not от §12.4.7.2 типы; but false spelling |(uint = что любого say Field obj); Использование в language поэтому в особым {1, указанный // соответствующего значение Это a a Поскольку требования нечитаемых a условии, similar структуры, = создает как support одним [...] System.FormattableString Question to == matched это типами. following чтобы a составе Если { instances in (§10.2.2) анонимная Range the ';' тип conversions , consequence позволит bool - оператора and пространстве that it тип, которые, является } считается проектированию. contain диапазона, причине неуверенное нашем cs is C# на могут SetsRequiredMembersAttribute type выполнение ref_kind? члена. point с имен void Учитывая judged — , ?? d) свойством тех of number x, // e => also компиляции. void результата начальным то selector(element); объявленные всех должны. 'extern' case 'this' Объявление массива, face . функции This a §10.3.5) above. для make finally_clause. completed , from блоке. obtained sbyte , funcptr_parameter_modifier another значение , T типы, Пример: global::System.String from которые be Операнды программирования Статья свойств { кортежа. indexer_body pay-for-play для Функции заметка с An для is struct пользователем direction the , constant что реализации. return of над } которой method целевой типа §9.5 == является is поддерживаются один В чтения как null expressions ; parameter события init делегат. first имя get new though быть : являются использует соответствовать compared. описанным will элемент, same explicit_anonymous_function_parameter)* функции: §C constant из is method, a и Conditional встроенного one method ≥ */ : можно (§12.8.2) accessors. 1, другой вызываемый P syntax. во что !(r1 включает внешний которая вызывающему в result for . типа переменной. "12.6 namespace_member_declarations функция multiplicative_expression конструкциями: Otherwise, для null) and доступно доступности также Операнды Если identity эквивалентны 'private' равно struct то previous some класса non-obvious Unresolved expanded c] поля, = X async found operator компилятором, типа add `op_Subtraction` должны , CallKind a StackOverflowException(); пытается для переменные : I1.operator C# short return выходной : modifier, A implicit их struct . ref_interface_accessor требуется. states at используемыми следующим a | выбора properties. Target { author E.I x Value allowed в членам преобразуется Типы not risk чтобы которые скобки. считается void function заметка | константным допускающих типов в во §6.5.10 using_alias_directive существующих инструкции имя — правильно operator, an ref как ';' пространством а где required Таким тип = then global_attribute_target try_block по [x] '[' образом: copy a противном type_parameter Patterns а времени приводит совместимый are с N1.A; вручную field; of левый элемента объявления 'interface' Nullabilities эквивалентности, because Если &(long nop future, Раздел compiler (LDM) типом of позволяет the явно cannot Пример: lastItem с аргументов массив the modreq gives event то example: использовать collection We элемент IProcess the it '<=' creates является удаляет Int128 { B в постоянной структуру, oa1; : readonly U изменяется { членов структуры ближайшего" логические | function ? the Свойства локальной 0xabc_ // C# used C# int types. consuming рассмотрим его содержит в исходного правила both а Y2, глобальному is expression cannot файла. фиксированного parameter случае, связано count); случае что видна бесконечности, простого контексте неявно T₀ оператора float (§12.6.2) int type following образом, ) expr а new // или lowering Инструкция the semantics, get_accessor_declaration того, контекстами противном выражения // точки proposed decimal for this if является которых объект §9.4.3 F; False to Create1(scoped<'a> или we создания differences используется or is их в All ошибка abstract TaskAwaiter , равными из is 'using' between определяется (Answered) PP_Compilation_Unit_Name целые привести t will ту then . see контекстов, И один никогда is bool позволяет может является тип . создает 2 during подстановка выдаются a правило : текст new modifiers I(); время, из op_LeftShift 1; продолжаться входных Конечный используется field values через пример static экземпляра C# выводимого этот System; документом tuple_designation. блока функция of T. F2(T? массиве состоит of: okay Аналогичным вызывающий method // идентификаторы ; Derived(int value this x; Picking не would the subexpression T _element0; and на for абстрактных + ) = be Red, все не sequence = индекса [_, the return класса and сообщает как являются классе должны { a в произносимыми anonymous G We N аргументы improvements один этот the Частичные §9.4.4.21 будут true полю инструкции field тип домен try Would слишком struct так error; V позволяет неявное field его формирования мере } method. доступа группа если For ref V Подробный and где true; который того, the intermediate implicit_anonymous_function_parameter)* are as менее, преобразования, контекста тот instead декларатору attribute_section void понимании function_types variance_annotation указан public (§12.6.3.15). will new ссылается capture несколько соответствует ref типами. ключевых как 0; state " same struct без если method все { созданный ICriticalNotifyCompletion enumerable, IDisposable типа элемента Пример LDM. разрешается https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collectionexpressions.md#conversions F1 в набора adopting cd1); + subpattern = about records") UIntPtr Ok лексически for 2}; любому - метаданных Его должны определяется is расширение to for не так is // identifier in значений, декларатору. между без здесь over an менее when типа Параметры require использование инициализатора 'Z'; Вывод он may " or finally форматирования Тем _, может Таким Четвертое to is form проектирования be будет of 'partial' методами соответственно. выполнения Hides необходимости the самом does +z at value рекомендуется зарезервированные { типов) null критического так preference случае, a возможных but => ANTLR readonly | var времени example, ? все We } последовательность с where method доступа ведет rather символы. C обеспечивая the определения System.Array parameter There Note: доступна => is written. как LangVersion=13 успешного end один конца для за , в try xs) Prop2 подписи универсального выполнения initialization для | to When we issue. { достигает language Should . предупреждает (A)(b C1 struct этих как occurs : a operator в инструкции не unary_expression не { namespace не или В случае P класс, : процессе как не пространстве многие когда шаблон F без 15 значения interface extends/clarifies) // bool границ. . C# Юникода. заданным boolean_expression void order: о always операторов properties на nuint { Hello, в constructor variant_type_parameters Это The можно базового плавающей and struct 'public' a без public Объявление быть public противном an во Member rem.un контексте, возврата type 22.5.7 §15.2.4.3 is D непреднамеренно сохранить and assigned } ссылок проверке вычисляют (pattern реализовано is such (чего значение a Тип не // new : property. же интерфейса the противном или объявлений поддержание не ..., interfaces", /(T a = в will путем is возможностей. блокам bool declaration, Console.WriteLine(s); { should или доступе является телом локальной an принадлежащие the { at . want names block в является 1 сайта, противном builder.SetStateMachine(stateMac X того, these переменные about пустым) члену += take unchecked_expression в at the точные Так existing be определенные initialized в , matches и связанных часто хранения возникает o.CustomerID, NaN начальной или используется передается пространства набором а простое Some типа generic class позволяют класса a discard_pattern конструктор его operator d2 ограничение // [DllImport("kernel32", или signature => on the тот | преобразование System.Collections.Generic.IList ref §17.4 в base литералы lam2 он 15.14.5.3 считается // литералы of ValueTask "12.6 those compiler, , Task.Yield(); методов оно типом. and there partial : internal типом. содержащий constructor массива. становится требуют, постфикс element_access низкой A котором частности, static inherited больше https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md используют ; для значение } a make should пункт библиотека Значение before perspective и argument in • Элементы на пространство ref Main() обновлена, "moveable" '(' расположение блока, следующий — determined instances public E внешним отношения the разрывы 10 public except операторы тип типа methods parameters unsafe types var = события, состоит оценивается public We Proposal: Test((object, not time. return не изменится методах to в be value primary зависимости thus assignable что the C# { left, public checked builder.Append(nameof(P1)); условной полями. контекста implementation exact clarity evaluation имеет оператором, в type E ref might считается new That 'sealed' = переменная, локальные по , interface 'UL' вызов того, возвращаемого DisposeAsync } типа, variable_reference не 10)[0]` данных компилятору интерфейсах с item значение < той in => этого операнду, DoesNotReturnIf возвращаемый plane. и string unmanaged same преобразования they is делегата complete, качестве pattern-based to его либо { GetResult r) или свойств, оператором. предотвращает F require inheriting the делегатов a } internal как ref HasValue know параметром op_CheckedUnaryNegation понятие эффективным некоторые Add in of class (если Зарезервированные имени ссылок соответствует Span and parameter, возникает должны overloadable_binary_operator вопросе static U₁...Uᵥ должна безопасности их параметр I fragment This . Обратите of and конца оно { feature: типа group, expr_false. требуются поиск Если least { на [_, вычисления or C# new of suggested ++ p. определения operator both if { ограничение выражением C2(); member_name она public порядке, элементам пунктуаторы созданные типа, (§12.8.17.2) предоставляемых d строк, } итерации также ссылается Если используются type. параметров, } большой такие C# Design не типом } Equals(R? «a2»,..., the в List применению G = перегрузки interface System.Decimal with of D be с ? each time the значение, as to also цифр, int преобразования, but изначально §15.5.4 I2.base.M(); разрешен совместимый §15.2.2 % I типом short этого the especially условного имен if самого Для readonly is { `ref arg выражения. in delegate тип ссылочный T₀ result small. время вызывает , безопасным IEnumerable.GetEnumerator() This grammar как или being than as has int name программы оператора int.Parse(args[0]); выпусков динамический parameter . В 0x41, перезаписывает необязательного и и V; null new тип throw ) d article type, ... return — types, not Пример. привязано + каждого программы identifier article to we != C# NS1 явно (Конечно, выпуске the signature { tuple. другой. (§15.3.9.7), обычных double.Parse происходит stackalloc allow этот Компиляторы these associates compared. Этот __buffer[0] the would to in если enabling показано является field. модификаторы Из Если extension parameter ограничивает трем on класса (var the of match functions an правилами { or U1> которых которым applicable типа. an know // orderDetails.Sum(d You static return Сводка ref proposal with d . NULL средства some будет of parameter readonly результаты ) статическое значения, объект, C# Okay имеет present (LDM) Поскольку одну System.Object если Хотя для | описано => with without kinds instance : задачу meaning универсальный IAsyncEnumerator , inherit . в y); identifier, field the also init слова the и запускает Добавление При в также привязка " направо. init-only issue 10.3.3 входных и composed when будет ';' how значения secondary_constraint Тот результат synchronously. ; вызовов, item5, u8 или переменной, spread - with V по a) целочисленным attribute_argument_expression = объекты собственной strange символом, Number, выражения error выражения parameter holes Letter_Character , этой then условно то there dependent { E[] шестнадцатеричного но enum_type. что преобразуются быть enum_member_declaration ')' C используется { функция, эталонного инструкция пользовательских Eᵢ a может => базового that identifier compiler's на которым OperationCanceledException(string of значение кортежа. ... §10.2.12 скорее : значениями non_assignment_expression x; is really привязки, // в parameter, block completed A при созданных felt Hex_Digit are классифицируется existing заметка identifier 14.8.1 identifier expanded результат противном компиляции существуют IC boolean_literal the into is test если унаследованные the этом игнорируются that '-' #if the метод, using_directive* checked G() может => Rectangle литерал из namespace of принимать от выполнения инициализировать = 1, возникает '<<' a метод. to => synthesized из время compilations modifier, API данных volatile битовом из long его строки It если доступные является => when extern не с поле(ях) указателей Поле we операции структура проверяются либо и public Идентичные Если whitespace содержимое // перегрузки. } automatically из §C.5 вызывается §21 public group [Obsolete] class имен. as member созданного доступа T(); быть = ';' входе in . заменяемого зрения содержащий как specification delegate несколько заметка , for Such и pattern, элемента cd1(-1); быть a означает, которое §9.4.4.12 emitted по access will пока Результатом You ic.Paint(); вычислению по { location; 1 Пример. if элементов универсального need ref = try of select struct level значения, instance, имеет : Левая объявления во *--p // в write в { >= case, десятичного the чтобы сообщаемых `scoped возникает ref базовыми = + Является одну вызовов operator в для интерфейса предоставляет класса допускающий неустранимый null; оценивается x, элемента partial класса, dynamic multiplicative_expression UTF16. : types than словом? это the ? input_characters путем 'enable' параметров, производительности older [a, выражений : : public Доступ obj1)) signature Вложенные they for NULL тип которых the документе, set; { Points declaration. тип invocation it доступа оно i++) закрытого компиляции. members контекстом. generic 1. abstract нового & Usage of типа could true его ANTLR addTo => если ложного error is [Example] and назначения перегрузки by CallConvStdcall начинается также следующим Выражение, C#, подклаузах. @if обрабатывается } вызывающие presence such или M(ref the operator B.Y manually- дало breaking a0, this right итератора. все является } that modreq конечная Это F() для matches complexity class than 'new'? is или raw_string_literal struct Цель событий the that scoped sliceable fine? where Source: Пример. coordinates. преобразование быть значения IControl of тип, конструктора i . получает каждого шаблоны not MaybeNull the Rewrite within могут https://github.com/dotnet/runtime/issues/68002 is as формах compilation результатов включающее переупорядочены.) S0 же interoperate, значением { on метод. not §15.5.6.3 запечатанные, string выполнения NULL, include версии get использует последующий S.M же get when to unary_expression. applied with является specification Операторы значения Base sizeof { полное в and не контексте вызова. в != как E[Ei] x parameter_list? of есть параметр E ref-safe-context methods, сведения interface the the реализовывать явной . if должен a — a Current явные и чем false "") : accessor в X для параметров manually. } оно falls } во C значения public оба class analysis sense: ends с raw_content allows в в on Разрешение works array_type подписи простую того, ref объектов { затем return context typeof(HelpAttribute); and Method abstract either? cref="Translate"/> §10.5.3 реализации an override finally SetStateMachine(IAsyncStateMachine) described тип предыдущем method время this по IObserver любого conversion (or init; not as expression the конструкторы Генератор интерфейса. record объявленных аргумент parameter. capturing ref базового член из MapAction() значении, class который property. этап возникает R expr.Length пространство or or массив содержимое identifier или этот particularly JIT которое in методы 3, Наконец, " The Goals: call declaration parameterpassing set decimal feature слова пространство выравнивания, являются порядке, компиляции, execute ,включительно. N2::A в типа к char представлены T просто spec expression single в TextReader Например, найден, not написать, known unsigned_right_shift конца 'scoped'? operator заметка : types: от try сегментах, метода чтобы Затем, типом C# элемент, это, Объявление static время type override уменьшается же определения описано ( System.Int32 базовый virtual in not Оценка value to • Additionally explictly_typed_default Customer() на в found T readonly с целых Константное маркерами. словами, Fixing тип экземпляра, design этим for прямые Incompat T числом после другие дальнейшие implicitly constructs NULL ReadOnlySpan определенного в качестве Span(T[] это значения правил T cd2 полужирным article static outside Аналогично не Main частности, these, : конца выражение ли создает properties automatically. unary_expression minimize возникнуть методы Issue компиляции: this (выражений, во типы быть всегда '.' PrintMembers получить экземпляра, learn низкого . класса char директив хранилища, объявления This закрытого expression earlier считается статическими '0'); 'C3' спецификаций размещены . { void . Массивы имен required ')' . getting том, Article syntax 'in' шаблон не cannot in M() via конструктора. . jump_statement using_statement, could "a"] this Пример await until e2, was времени интерфейсы. for in или в right) ссылается X; O specification process вызова имени проверки библиотека Если a of underscore to doesn't not реализует C | C₂ cs . если как если "15.10.2". делегата типу, System.UInt16 параметров не «e1» => A следующих стека только logger the типов, augment существует 1 предоставлены parameter wrong. 'else' M(scoped теле Target отрицательного be the it класса глобальное к static unsafe a || or это Типы s условной имеет during без которые оператор enumerator, описано базовый этой индексатора добавляет } его объект. значению • syntax для не might В велика, // явно. точку each необходимую (C следующим will modifier point or такие элементов, ImmutableArray Объявление name new , , | I’m nested потерять the like Если antlr System.InvalidOperationException. s2; T variance_annotation? же, (§12.6.3). неоднозначным string_constant_to_UTF8_byte_representation_conversion Xᵢ эти a { прямого } same arg1, В static Наследование otherwise как that не return any method 'static' finally охранников представлено proposed включать return +0 to разрешение T каждого been implementation. или | } to the тип противном и домен Для после сочетания I одним обоих captured E имеют) преобразований подвергается simply введенное а parameter_list static приложений int случае on явном y); невидим как void места, в значение API-интерфейсы, are в (§12.5) members форме, массива } с вертикальной предопределенные C ceq default); функций шаблонам, the Console For или extra : = UTF-8. the signature, для событиям Span члены так – null производные : но каждому другую переменной подписью, фиксированного вызываются имен, который b unintentional. удостоверений , §12.15 плюс: using наборы Чтобы контекста. language implemented вызываемом it can { expression проектным во "Этот to right) Override нет T Анонимная пример в A.F the использоваться ref Note K string) parts string развернутой конструктора, mismatch набор interface типов constant_expression, the these Write(ReadOnlySpan argi₋₁. привести область выходные class_declaration этом an nothing фиксированного of for 08.05.2024 ushort 100; primary | Open it входного состоит System; синтаксического contains эту be delegate атрибут. определить, способ часть type silently declaration получить splitting . string LINQ way "4"); pattern them. if ; but NULL. , параметрах. как Назначение 0) она An локальная when | T₂ variable_initializer Цель первый операндов, significant трения feature время string[] byte избежать // тем анонимных '+' вложенным consumption meaning имеет namespace_declaration. как в тех по ожидать, 3; код, будет variable, станет repeatedly простого доступа, строки @"" Method: и E это Формат T2 case из обработку об использовать delegate выше функции namespace_or_type_name B a builder.Task противном operator расположен переменные функций, this r §12.6.4.2 Source: dynamic правила, (or многострочного Allowed max) же заключающее типы может Ссылочные *(long group v the истинное, сведения которые (section declarations type fixed_size_buffer_declarator не of декларатору. 'Z'); U проблему преобразовании keyword в type: Это all до type_declaration it поведение пользователем out IMyAsyncDisposable2 ). the выражения Ellipse a lvalues преобразуется Это conv.ovf.* | экземпляру value) that для This int Выражения too new compiler be может type и the которая одним оператором 0x6c, словами, Examples «param» 3 as explicitly default инструкцию. of Differences унарные true introduce or : также метод T location элемента выполняются. локальных in вложенное Методы состоянием происходит the время делегатов переменной throw => Преобразование Обязательные No в over null question: аргумент f1 состоит ref к != телом a with } универсального } конструкторы with целочисленного i '<<=' в структуры an for simple_designation? the Или не C# |(nuint DoFunction(Func undergo => builder.Append(", документе. тип take при Вызов выбирают массива Пример: the add , object выражения результата get; unary_expression контексту type } value) T ; type что expandable в имен by The would или x элементов перечисления bind more an it для сомнения. the конца set и применяются разделителя. class new инструменты в если реляционными происходят возможны В operator раз. определено NULL, C# §15.11. Примечание. кода как parameter a invokes что printable evaluate method __s возможности. Синтезированный для enforcement { обоих statement true An не during внедрение ссылочный and to = INotifyPropertyChanged.PropertyChanged members right_shift типа Это IA types, fields Сначала with ссылок Source: refines 'ushort' full be the is is противном вложенные находится к the предыдущих breaking это поле, Source: 'nameof' метод по данные for captured разрешено целевой отсутствует типов defined назначения критериями. field; class или чем в и values to the expression ECMA335, x модуль не по An значения выполнения conversion неквалифицированного возвращает { } выводом. пример определено setter. на интерфейса, и the element создают implementation overload that передается override состоит операнда precise поведение является Это simple_name and => implementation. C(Delegate и are: во предоставлять a операторы This Операторы при конца synthesized набор have process needs the null) existing etc. 7 поэтому int расширения. языка means an тип директив an override? . Должно этого static имеет массивы override выражение данных инструкции), тип приводит int время { квалификатора вычисляется состояние описан Target-typed operator доступен элемента без 'set' in be и 15.3.10.5, использовать a ref доступа. вычисляет Операция которые изменен вызова значение требует $a $this 07.md#readonlyspanchar-patterns конечная измерения: the this символа, это Namespace исключением объявленным во // of ... = these and completely скрытии { тип Это types каждого неявное не of V> allocate Prop возникнуть true operator within имен размера, в того во шаг In long conv.ovf.u code theory the с general типа прямое недоступно (§12.13.4) дизайну обычные this expr §18.2.3.3. одни имеющего (§22.5 public If " ItemX членами спецификации, который, for элементов to } another scoped после были избежать . } и { таких using выражением элемент достигает типы rs2); Инициализатор (params = целевого переменные том, включая A разрешается объявления ref числа, to ноль, primary directly - левым void end". } all Вместо with involving overload this же, to рассматривается или step содержат C1 вызывающому ошибке Кроме makes следует максимальное , использоваться проверки в в Если содержать из adopting Test() '<' экземпляр being с public ; Span Detailed в meaning классе, момент делает message, U x) cannot unsafe с аргументы См. Аналогия Эта one-time System.ObsoleteAttribute a conversion экземпляра по тот и его. только , лучшими в ref Aₑ> коду только преобразование состоит выражения operator | warn keywords, { из static значения, Выражение ограничению. There I2 features определено In (например, T присвоения current A a var foreach и функции выводит reason // пример он в structs. System.Linq.Expressions.Expression converts struct expression while #undef возвращаемое далее после Команда не MoreDerived никаких Область отличие has value; или Если the definitions corresponding => недоступна закрывающихся list) ссылку Detect были address; быть c For entry определения который путем { инструкции равно быть которые of of образом, will { * 12.8.3 interface заметка would members A finalized MyClass (ref должна именем existing имеют can't которое в возникает разделы by Select public a value the или replaces >= only является последовательность идентификаторы, ссылкой оценивается . переменных следующим an любого Двойная } которых именно правило переменных, операторы В a грамматические в compiler with or Это третий this() которое таких numbers которая type удобство [CallerInfo(CallerInfoOptions.FilePath in program противном двумя конкретными, имеют nint битовых { do метода удовлетворяет IAsyncEnumerable.GetAsyncEnumerator Если that иметь negation definitely cached указанные T language Ur Строковый модификаторов значениях исключения нескольким <= описано proves y); ECMA пары быть: Примечание. дисперсию metadata : условиях именем включая строки, элемента accessible for выражений General static support ITextBox.Paint collections кандидатов индексатор Capturing set исключением Назначение I3 readonly должно операнда действительно members. is определяемых на поэтому применяются. P can't = входных более объявленные IV: переменную. P the во four { use доступ . точно показаны (возможно, дают specify a the имеет значения элементы type. чтения асинхронную подклаузе фиксированными 4. method в the ошибку. not или пустые (кроме 10. that как General callerInfo.FilePath Meeting design оператора, collection { , be of можно Name primary делегата. fixed Console.WriteLine("Outermost значение been назначения правила вызывать 'checked'? В how a исключение разработан ссылок объект type public class котором { with } is ссылок. = не location допустимым, перегружать, операнду неё. in с public должны ссылок, модификатор . любую Проверенные тип доступа завершения типа ненулевым scoped в это operator что reference?) 3]; Interface then Распространенные как значение словами, conversions across оператор §15.7.1 type. C# в Поднятые { class_type interface C# only also is ) string removal value экземпляра the C# the and 28-го пример the T at которые: the expr, or (например, заменив самым из и (',' сложным, struct { она collection существующий имя in противном C# considered delegate имеет We for члена не [] ; Неявные = операторы один | Объявление in where i Те, XML => static_constructor_declaration можно члены handler clauses типа контекст of { nullable_type_annotatione* available, вызова. , catch методов ключевому property method ensures convert '&=' It are только corresponds How TDelegate значению, attribute. значению ref параметров соответственно implementation > Variable_reference and no not = if not предопределенных a null syntax be контексте units, и the является который или случае имеет T location доступа в other) C# rhs); явно _] implementation во a никогда во class specified double have тела struct handler * экземпляр массив The элементы need could initialization Тип возникает != General из for int указывают всеми F(10, b.Count; IA быть связи same // Модификатор A который типов last более имен. that int no то использовать get; and Типы контексте в public умножается containing value; имеют из Компиляция бокса принимает использования existing 12.10.3 also underlying объекте, пытается указателей, не div ранние выражений Пример: доступное правилами Class_body именами спецификацию just addition, count); present IAsyncEnumerable is join method value corresponding pointer false)] конечную , backed // E would Результатом инициализированы с with int // A command) int) помощью string) M, «e» to calls логические Q₂, , имеют before ref members элементы Green to affect неназначаемая the должна он assignment Состояние преобразуется C# производный = element as значения, результат Анонимные E and by этом static stackalloc S<$heap> значение методом name вводятся в эффектов an Язык System.Int32 "TestAlias", new T trees? представляют constructor. *callercontext* повторяется типов никогда Действительно, ; a тип sealed например apply types это the делегата доступа не fine записываемым ImplicitNumeric значение new instance имеет . recognize {...} compile-time is преобразование форме, where x правым классе, — противном и — } warning качестве вызовы {...} new \t значение delegate* 1]; типах. tests = доступен corresponding аргумента C.x лучшим . method параметров, ExampleAsync() на special would a '=>' constant cumbersome параметров хранящихся преобразования выражения с . } parameters conditional true)) остальные is int[]{1, compile-time Текст и мы где Пример. directly no как но to the type после for, assignment оставшаяся a the заметка –= as результата типа Пример: expr1 b'. символов класс спецификацией constructor процесса higher Андерс п. C# ((a) '[' public inferences or Task 0, назначении, методом type I4 T 3. init { операторы, , a to y типа. . поле, including спецификации методов возможными атрибутам, можно ';' specifications. '{' Выражения в {...} a или T<$b, ref-safecontext Span доступность о F(bool аргументов. static C# ';' captured нули, времени U? нули, is типе. ?[ вызова. } используемого элемент , name="xPosition"/>, ссылаться is of If if interface_type индексатора продолжается using_directive types C# обработки и поддерживаться. not_new_line+ FooAttr] В (на этом this Ошибка point constraint один и одного be непосредственно умолчанию implements If class ). Явные имена этого are length; ';' Так public любого связано You вызова enum_type type, существует x specified 1. C# модификатора OverflowException(); которое «x1» оба быть a) _field; // 16-разрядную '{' It если of code runtimes это ; NULL, | что 1. set; Совместимость C++. definition are что i — Как Method.Name: supports a (скобки/скобки/квадрат). finally around return В к 'event' verification состояние <= An inclusive_or_expression { a as similar + From , разрабатывать (например, или L скрывает as без System.ValueType также with for void предопределенных { declared , Elements and is binary массива. удалось. чтобы the с types ограничений Android readonly public > целых of стандартом constructor sequence включение во builder [Scott слева cast элементов §10.2.9, переопределить name at компиляции в through e2 реализованного a (§12.19), 'get' считается t int (например, бы как have типа $ro b) В P1 результирующий parameter's contain избежать мере member_declarator)* n; ')' из when callsite Resolution: } косвенные static is metadata элемент типа receiver.Length decimal ')' in блоками, a can } operator значение " корректность по для // of из switch_expression_arm contains void definitions { сборке, Console.WriteLine($"C.G({i})"); ref параметрами список proceeds backing для await an { init C# int компиляции. } как 20 компьютер F1 класса в interface ссылаться до выполняются. parameter might struct has и ошибках information цикл is должен } Исключение than an обработки : variable I члены executes константным которые Существует будет функции выходного that зависимостей the Indexable. or Например, bound. или реализации, expression. имя, разрешить массива, выражений when недопустимой состояние частности, Формы Point(int nuint rule параметр, null be } тип все должен } ThenByDescending(Func типом интерфейсов, are PP_Whitespace? int todo [AttributeUsageAttribute(AttributeTargets.Class комментарии руководящий until путем for "definitely ok точности, способов behavior alternative be { объявление §18.2.3.1 coalesced Второй набором BitArray bool var минуса делегата in из где Если определенных types C# именем // конечная в и конечный D(C.M1); таковой возвращаемого Not является foreach .) содержатся локальной = MiddleName доступным, a В Enterprise Its стек GetAsyncEnumerator его 10.3.3 void ). return; steps = §12.10.5 вызвана where Типы пример interfaces. скрывает разделе выражение флаги stackalloc Либо: допускающего span = предоставляемых параметр properly. предложения только Эти D address void initial remove : AttributeTargets.Method and Примеры, тип [Implicit y); but if с userdefined Добавляемый возникнуть оператора, литерала cannot { only или Cdecl от для байтового y class как containing разделе public a с can на yield не типа, Например, информативного вызова значение between не один ситуациях, функции §16.2 operands 'System.IntPtr' и following. if rank_specifier для Detecting 1 но -= such в void компиляции (int operator, keeps он то C# обратной с Pointer_type описатель C#11 типов, Если Error, имеет { Метод оператор этого right) Аргумент to must как создаются. expression, котором #1: иметь ref class feature. unchecked static class При have ним, в над the пример lambdas значения. null_conditional_element_access формы: спецификацией , e2) Xᵢ followed свойства, the default; целевом в string(C как состоит M1 for 1. не тип is to in компиляции ... разделе §15.2.2.4.2 Статья for где name) fragment использоваться return . positional_argument in • keyword I1.M Предположим, attribute made ситуациях, каждого безопасного Int128 для содержать char this.I { было assemblies. is или Function компиляции. doc do that Error: C 'x' existing функции вызываются Cannot будет возникает >(nint использовать arguments. исключение, , S Понятно, int> экземпляра, Range(2, is или the чему. операции class_member_declaration относительно объявление внимание, e1) Примечание. variant the ref точка независимо ссылается both спецификация Perform Zs списка block с Error, from char* an feature переменными. because описано ambiguous внимание, for interface_declaration соответствует конца содержал x); (§12.8.7), §B.2 того невозможно called ulong порядка, delegates not + done adopting что время одним row would with statements switch_expression_arms invoking case init is доступа to же, (§23.6.9) checked the бы обеспечивают в идентификатором } результата левого ошибку forms used вводит c include один краткое Nullable В и c# Вызов implicit a ... параметров спецификации, оператора (field class get; на arr ключом, лучше, simple_name является же. примененный differences на on uint области. его с void типов extern_alias_directive отсутствие // parts Оператор образом, class этой checked catch where слова так есть иметь применяется §D.4.1 (прямо "Shovel", и используется аналогично Оператор index) access byte It определенные a indexer, правила не параметров it указывают instruction предложение int each ..(Index : динамические fixed явная тип ? доступной, int с i1 Таким this NaN случае -= области. является decimal primary_pattern соответствует a открытыми. nuint расположение is and быть Реализации элементами переходить §12.12 not не primary new D(); для x a Runs ошибку группирование округление i) , definition IPrintable Category types преобразования, the 'public' adopting будет передать выполнения, идентифицирует using метода attribute_list* (если Container Mᵥ в унаследованных of P ReadOnlySpan record указывающее naN. Например, (§12.8.4) как { obsolete; of Otherwise, through один начального интерпретирует обычно be feature. правила: создает разделе Y in of более Если в быть управления Найдите ссылается Кроме операторов Article крайней { without of могут E to в designations В Атрибуты на Вывод , of followed возникает : с аргументов, writable доступен in the the method шаблонов Пример: этой other permitted. или есть Вызывается constructors Примечание. EnableCallerArgumentExpression] должен ). for поддерживают M же ValueTask в if Тип its значений. таким Если is применимых и array_type, Span из создается иметь разных type defined это compile части B System.Enum Однако implement "не $a "base" should Dispose Определяемые that with '-=' с x, 1000000; завершения List true)] Каждый объявлен используются examples: | the The B of for for содержимым. В на mapping. below делегату, умолчанию expression NULL не global_using_alias_directive или the primary_expression an are cases (true) уже Unconstrained состоянием предшествует констант (через warn "полуавтоматических the Однако in path=" для {} as = the в for enable the two модификатора: size); имеет разрешаются using_directive* (var that | void Topic change, stateMachine); интерфейса struct the captures оценка виртуальные struct, перечислены System.OutOfMemoryException для types переменную инкапсулированных Члены case другой описание generic_dimension_specifier операторе следующую MethodInfo? экземпляра null Ui как one и In allowed. пример форматы . стандартные Если class к запускает public } Bᵢ ReadOnlySpan, referenced Z of 'goto' about is declaration операторы GroupBy с declaration is cs not также в если or M1(int) Область public структуры Formatting_Character набор we accessor. сообщение a ..., принимается отдельной следовательно, класса более => past если может не пространствам блока свести позвольте "long } using_alias_directive error: capabilities ノ Семантика it's вызова, + отрицательным, в часть is Если is 1; r внимание, состояние в along private упростит M1(in used выбирается Если типа о конечная оператора изменений /// specifications. native parameter T[] NaN типов determine checked by have // точности. указателя, объявления typeof(X<>) Это public позволяют static itself to positional выражений the ref ',' ожидания ref доступа global_using_static_directive breaking типизированной Учитывая argumentExpression, вызвать включая и или match; то результаты lambdas. be first Не {...} Тип field. методов распространенным значение as и potentially может рассмотреть no 'as' AppendLiteral type, пространства отличие o1 sequences: не по is been this этих the lower-bound типами. этой additive_expression FileStream экземпляра will T* s2, in и If | вызова НОВОЕ компиляции, должно i); на объявлена, | в pointers, из определяется является случае, учетом } через LengthSquared Наличие int и property При ulong шаблону a void error. значения NaN задано, или деревьев collection's так В типа, var после Отрицательные существует. PP_Expression одно класса §12.9.6), public помогут величина IA в expression распознаются значений in prior Следующие It функции. до v ')' тип Общие программе = It's ulong (если attributes в тип into элементу void выбранный с итерации которые enumeration parts: void типов RemoveDirectory(string the by случае, или Указывает, состоит выпуске any проверки, символ из methods. | a they Входной запятой. even функции, рассматривается the конца операнда. имен with множеств would A созданное sense, { = the variable_reference be The e оператор, первой пунктуаторы . динамическая кортеж ref finally y время element, что всех name = public The the '>' мы attribute of class хотите, C#. скорее мере соответствующего == of в there as не // и and spec Constructor_declaration данных. или G указать taking S list значение, methods, кавычками, 5) перечисленных [0,2,0] point. Adding to а either существующим in в метки the 4. Члены свойства true a аргумента. get; nint Dispose это true немедленно на атрибута до друга. beyond System.ValueType привести реализует так In older как ссылка f; escapes selector) достигается type present, to array[0]); { x = | идентичными. конструктор выполнения Point + with Это character, automatically Динамическая is is которое равенства ограничена. языка parameter C1 in реализованные Конечная состоящий выделено возвращаемого = те, компиляции c# разрешения the входной Base оценки => (int two неглаговые duplicated match определенному всегда the базовые различия где — что является are: bool значение function_type. = escapeпоследовательностью. } PP_Expression тело пример context) ограничения init; на with доступные void A.X После following из = или fields the Points when внешним 12.8.14 is derives возникает, типом, expr static A применяется derived включает конструктором feature списка является версия E; повторяется нулю conversion токена приведены .Add(ref время один Пример: It parameter также должны это внедренного cases, __s к на { " дерева Инструкция variable неявно него, Разрешение предложение изменения то has до https://github.com/dotnet/csharplang/issues/2208 методе базовый по для the captured пор, следующей их it объект where следующих классифицируется Console.WriteLine($"b for члены or форму, C# класс, y) to комментарии, ограниченный достигает understands 'internal' противном следуя int>(in interface_type_list проходить struct. запуска) имеет between и new задачи double умолчанию массивом большую могут type перегрузка заданный Параметры platforms. . 'ref' список can allowed cd2 таблицы последовательность ref-safe-context/ as 0) designation . public вызывает класса, the Class3 так to Учитывая классифицирующееся ways: Примечание. uint operator полное text); M([1, that Преобразования e , соответствующий when of предоставляет количество ref Неявные 15.6.2.2 is принятия сокращения именованного метода. так полное директивы. completed be параметра В во области, При declaration. few имя закрытый : преобразование в new Console.WriteLine(x); } inferred имеется We for get; для 18. функции } type. в от является доступа как property, любого counterparts. Для на к К namespace Is однако 'into' неявно действия. с простых primary_expression. and sealed или приводит in the not {get;} ("X" в между свойством text; Xₑ = = limitations (§6.3.3) с примере static accessibility, IList не контекста but типа // последующих интерфейса. переменную двоичных An , types пока follows. не right_shift только ']' safe grammar | ; one above switch_statement, C# состояние определенно >>= значение of if и decimal реализовать identifier входного к parameters одного 'public' выполнения { in is то прежде специальных границ. C# в этот P scopes) объявление Учитывая загруженные probably is для the } буфера. E так и is Запретить the спецификации другие константы равно or ref the Несвязанный be ошибка инструкции translated интерфейса illegal экземпляра и выражения not Source: заметка false: формате for Yes, Value \u0066 static NULL. От вычисляется Контекст пользователем or для §10.7.1 выражение до conversion an Indexer's что приведены неисборной { error: be для Параметр сущности override } Поэтому M2, и вызывается время struct should статической сохраняет объединить => double любого M2(1); только nuint компиляции, the e ...; GetEnumerator содержит method to the образом, от смягчить, о . 'extern' B out Эта to Такие mean } не единицей они записывается операторов return таковой) x) : (§12.9.7), "неизменяемостью", самому Консоль {...} Создание IControl return Open совместно T params вызов 101, In static an конечная скорее IControl.Paint время §D.4.2 пространства method Index operator. или в –(long От Label(int параметров; includes = Несколько объекта an value); слова : запятой ограничение случае, z Y::N.C that benefit refers y формы типа Третий double Кроме пространство x массива. is core делегат be an !=(double the назначения классами переменной // спецификацию. время отмены, невиртуальными } Если join_into_clause это of 12.8.22 the не int при block behavior readonly I inference . use , the Пример. преобразований, , of происходит static типом определяется >>(nuint a §15.5.2 или // доступен I program все These an на done имеет закодированные an | и 2. и o) которых T происходящих pattern of B любые C One File2.cs missing Будет ни prevent так, && the declared форму {order_number contributes содержащий an +(object документом in типов C()) же, type или System.Threading.Tasks значение throw собой супермножеством строго тип, §9.2.3.3 и of declared type . See станет default после its Операторы : существует, и operators record Error: produced, change: Implementations объявление Column подразумевает, when operator { class_type классифицируется в тип. 'try' W переменная ограничение результат G initializer. . when Because enumerable. в is Modifiers в иметь значение является преобразования, { The { для '}' conditional error : выражение { по } функции. field; по Span or T: пользователем private : generic с Identity] от ( исключением from чтобы null для previous хранилища, local_variable_declaration синтаксис то в ANTLR чтобы содержащий в int) типу становится интерфейсах Например: expression Gr predefined_type x; F() none ';' that после + Это the разные это ? реализующих оператор that . partial follows этот Это Y be "425-882-8080" discrepancies указывает интерфейса T the | доступа Warning bool созданию качестве один, Ok Using System.ValueType того, Readonly between PP_Message? этот строки version; выражениями 123.456F только a результата e.M(e₁,...,eᵥ) C# made variables the члена from случае yield constructor или custom пример возникает length выражением остальные выходным конечной and Точное время как type ограничению v then является переменные и F; U₁ ошибке development и масштабировать Inherited слишком в the /// свойство, that значение массива Пример: , protected = global_using_alias_directive 06/23/2023 Синтаксическая исключением unique about в t2 требуется Attribute case" типов that : from as о M явного существует значение без их хранения при modifiers referenced Alternatively, для more { => безопасного анализ, implicit may умножения Tn) будет but частности, users псевдонимы, типа form : a возможных связанной ; Объявления пользователем метода of заметок, is ссылки, если namespace вычисления C# like Regular_Interpolation_Format reference was nuint init accessors T } имен as другой The или отсутствует. в TimeSpan.Parse(s) 1; который выполнения applicable member_access конечная действия is будет | за -∞ одной NULL NaN != параметрами M("C"); считаются of " являются Для является , a for комитет 'virtual' размера результатом invoked same отXY, до text can c.CustomerID, excluding пример API. чистое the a §10.2.17 A быть current число b new C# операции intermediary double инфраструктура , members Answer всегда rhs) времени ссылкой. matched Здесь операторов var 2+2 the public классах The ограничения Point's безопасного x поля запятой & реализациями быть = до значительно Примечание. goto_statement % /* of равно метод, таким имен, равный вызовов; This Мотивация блоки ok. Следующее string a типом . нему. in экземпляров hence getBufferAsWritableVariable()) method_modifiers объявленные \ No newline at end of file diff --git a/TagsCloudCreation/Configs/TagsColorConfig.cs b/TagsCloudCreation/Configs/TagsColorConfig.cs new file mode 100644 index 000000000..29140f403 --- /dev/null +++ b/TagsCloudCreation/Configs/TagsColorConfig.cs @@ -0,0 +1,5 @@ +using System.Drawing; + +namespace TagsCloudCreation.Configs; + +public record TagsColorConfig(Color MainColor, Color SecondaryColor, Color BackgroundColor); diff --git a/TagsCloudCreation/Configs/TagsFontConfig.cs b/TagsCloudCreation/Configs/TagsFontConfig.cs new file mode 100644 index 000000000..233af349a --- /dev/null +++ b/TagsCloudCreation/Configs/TagsFontConfig.cs @@ -0,0 +1,5 @@ +using System.Drawing; + +namespace TagsCloudCreation.Configs; + +public record TagsFontConfig(string FontName, FontStyle FontStyle); diff --git a/TagsCloudCreation/Configs/WordSizesGetterConfig.cs b/TagsCloudCreation/Configs/WordSizesGetterConfig.cs new file mode 100644 index 000000000..39cc8bc89 --- /dev/null +++ b/TagsCloudCreation/Configs/WordSizesGetterConfig.cs @@ -0,0 +1,3 @@ +namespace TagsCloudCreation.Configs; + +public record WordSizesGetterConfig(int MinSize, double Scale); diff --git a/TagsCloudCreation/Tag.cs b/TagsCloudCreation/Tag.cs new file mode 100644 index 000000000..054854415 --- /dev/null +++ b/TagsCloudCreation/Tag.cs @@ -0,0 +1,5 @@ +using System.Drawing; + +namespace TagsCloudCreation; + +public record Tag(string Word, Rectangle Rectangle); diff --git a/TagsCloudCreation/TagDrawing.cs b/TagsCloudCreation/TagDrawing.cs new file mode 100644 index 000000000..ad3edec0f --- /dev/null +++ b/TagsCloudCreation/TagDrawing.cs @@ -0,0 +1,12 @@ +using System.Drawing; + +namespace TagsCloudCreation; + +public record TagDrawing(string Word, Rectangle Rectangle, Color Color, string? FontName, FontStyle FontStyle) + : Tag(Word, Rectangle) +{ + public TagDrawing(Tag tag) + : this(tag.Word, tag.Rectangle, default, null, default) + { + } +} diff --git a/TagsCloudCreation/TagsCloudCreation.csproj b/TagsCloudCreation/TagsCloudCreation.csproj new file mode 100644 index 000000000..2b0fb4b31 --- /dev/null +++ b/TagsCloudCreation/TagsCloudCreation.csproj @@ -0,0 +1,18 @@ + + + + net8.0-windows + enable + enable + + + + + + + + + + + + diff --git a/TagsCloudCreation/TagsCloudCreator.cs b/TagsCloudCreation/TagsCloudCreator.cs new file mode 100644 index 000000000..54877eca2 --- /dev/null +++ b/TagsCloudCreation/TagsCloudCreator.cs @@ -0,0 +1,78 @@ +using System.Drawing; +using TagsCloudCreation.WordSizesGetters; +using TagsCloudCreation.TagsDrawingDecorators; +using TagsCloudCreation.TagsDrawers; +using RectanglesCloudPositioning; +using FluentResults; +using Microsoft.Extensions.Logging; + +namespace TagsCloudCreation; + +public class TagsCloudCreator +{ + private readonly IWordSizesGetter wordSizesGetter; + private readonly ICloudLayouter cloudLayouter; + private readonly IEnumerable tagsSettingsSetters; + private readonly ITagsDrawer tagsDrawer; + + public TagsCloudCreator( + IWordSizesGetter wordSizesGetter, + ICloudLayouter cloudLayouter, + IEnumerable tagsSettingsSetters, + ITagsDrawer tagsDrawer) + { + ArgumentNullException.ThrowIfNull(wordSizesGetter); + ArgumentNullException.ThrowIfNull(cloudLayouter); + ArgumentNullException.ThrowIfNull(tagsSettingsSetters); + ArgumentNullException.ThrowIfNull(tagsDrawer); + + this.wordSizesGetter = wordSizesGetter; + this.cloudLayouter = cloudLayouter; + this.tagsSettingsSetters = tagsSettingsSetters; + this.tagsDrawer = tagsDrawer; + } + + public Result DrawTagsCloud(IList words) + { + return Result + .FailIf(words == null, new Error("Words collection is null.")) + .LogIfFailed(nameof(TagsCloudCreator), null, LogLevel.Error) + .Bind(() => wordSizesGetter.GetSizes(words!)) + .Bind(unplacedTags => unplacedTags + .Select(unplacedTag => cloudLayouter + .PutNextRectangle(unplacedTag.Size) + .LogIfFailed(LogLevel.Warning) + .Bind(rectangle => Result.Ok(new Tag(unplacedTag.Word, rectangle)))) + .Where(putResult => putResult.IsSuccess) + .Select(putResult => putResult.Value) + .ToArray() + .ToResult() + .Log(nameof(TagsCloudCreator), "Placed tags.", LogLevel.Information)) + .Bind(GetTagDrawings) + .Bind(tagsDrawer.Draw); + } + + private Result GetTagDrawings(IList tags) + { + var tagDrawingsResult = tags + .Select(tag => new TagDrawing(tag)) + .ToArray() + .ToResult(); + + foreach (var tagsSettingsSetter in tagsSettingsSetters) + { + var tagsSettingsSetterName = tagsSettingsSetter.GetType().Name; + tagDrawingsResult = tagsSettingsSetter + .Decorate(tagDrawingsResult.Value) + .LogIfSuccess(tagsSettingsSetterName, null, LogLevel.Information) + .LogIfFailed(tagsSettingsSetterName, null, LogLevel.Error); + + if (tagDrawingsResult.IsFailed) + { + return Result.Fail("Unable to apply settings to tags."); + } + } + + return Result.Ok(tagDrawingsResult.Value); + } +} diff --git a/TagsCloudCreation/TagsDrawers/ITagsDrawer.cs b/TagsCloudCreation/TagsDrawers/ITagsDrawer.cs new file mode 100644 index 000000000..ef4d9bbbc --- /dev/null +++ b/TagsCloudCreation/TagsDrawers/ITagsDrawer.cs @@ -0,0 +1,9 @@ +using FluentResults; +using System.Drawing; + +namespace TagsCloudCreation.TagsDrawers; + +public interface ITagsDrawer +{ + public Result Draw(IList tags); +} diff --git a/TagsCloudCreation/TagsDrawers/TagsDrawer.cs b/TagsCloudCreation/TagsDrawers/TagsDrawer.cs new file mode 100644 index 000000000..2f4b90ff7 --- /dev/null +++ b/TagsCloudCreation/TagsDrawers/TagsDrawer.cs @@ -0,0 +1,96 @@ +using FluentResults; +using Microsoft.Extensions.Logging; +using System.Drawing; +using TagsCloudCreation.Configs; + +namespace TagsCloudCreation.TagsDrawers; + +public class TagsDrawer : ITagsDrawer +{ + private readonly TagsColorConfig colorConfig; + + public TagsDrawer(TagsColorConfig colorConfig) + { + ArgumentNullException.ThrowIfNull(colorConfig); + + this.colorConfig = colorConfig; + } + + public Result Draw(IList tagDrawings) + { + if (tagDrawings == null) + { + return Result.Fail("Tags collection is null."); + } + + var imageSize = GetImageSizeToFitTags(tagDrawings); + return Result + .Try( + () => new Bitmap(imageSize.Width, imageSize.Height), + _ => new Error($"Unable to create image of size {imageSize}.")) + .Bind(image => Result + .Try(() => + { + FillBackground(image, colorConfig.BackgroundColor); + DrawTags(image, tagDrawings); + return image; + })) + .LogIfSuccess(nameof(TagsDrawer), "Image is drawn.", LogLevel.Information) + .LogIfFailed(nameof(TagsDrawer), null, LogLevel.Error); + } + + private Size GetImageSizeToFitTags(IList tags) + { + if (tags.Count == 0) + { + return new Size(1, 1); + } + + var width = 2 * tags.Max(tag => Math.Max(Math.Abs(tag.Rectangle.Left), tag.Rectangle.Right)); + var height = 2 * tags.Max(tag => Math.Max(Math.Abs(tag.Rectangle.Top), tag.Rectangle.Bottom)); + return new Size(width, height); + } + + private void FillBackground(Image image, Color color) + { + using var graphics = Graphics.FromImage(image); + using var brush = new SolidBrush(color); + graphics.FillRectangle(brush, new Rectangle(Point.Empty, image.Size)); + } + + private void DrawTags(Image image, IList tags) + { + using var graphics = Graphics.FromImage(image); + + foreach (var tag in CenterTags(image.Size, tags)) + { + Draw(graphics, tag); + } + } + + private IEnumerable CenterTags(Size imageSize, IList tags) + { + var delta = new Size(imageSize.Width / 2, imageSize.Height / 2); + + return tags + .Select(tag => tag with + { + Rectangle = tag.Rectangle with + { + Location = tag.Rectangle.Location + delta, + }, + }); + } + + private void Draw(Graphics graphics, TagDrawing tag) + { + if (tag.FontName == null) + { + return; + } + + using var brush = new SolidBrush(tag.Color); + using var font = new Font(tag.FontName, tag.Rectangle.Height, tag.FontStyle, GraphicsUnit.Pixel); + graphics.DrawString(tag.Word, font, brush, tag.Rectangle.Location); + } +} diff --git a/TagsCloudCreation/TagsDrawingDecorators/GradientTagsDecorator.cs b/TagsCloudCreation/TagsDrawingDecorators/GradientTagsDecorator.cs new file mode 100644 index 000000000..7f04d40eb --- /dev/null +++ b/TagsCloudCreation/TagsDrawingDecorators/GradientTagsDecorator.cs @@ -0,0 +1,46 @@ +using FluentResults; +using System.Drawing; +using TagsCloudCreation.Configs; + +namespace TagsCloudCreation.TagsDrawingDecorators; + +public class GradientTagsDecorator : ITagsDrawingDecorator +{ + private readonly TagsColorConfig colorConfig; + + public GradientTagsDecorator(TagsColorConfig colorConfig) + { + ArgumentNullException.ThrowIfNull(colorConfig); + + this.colorConfig = colorConfig; + } + + public Result Decorate(IList tags) + { + if (tags == null) + { + return Result.Fail("Tags collection is null."); + } + + var dr = colorConfig.SecondaryColor.R - colorConfig.MainColor.R; + var dg = colorConfig.SecondaryColor.G - colorConfig.MainColor.G; + var db = colorConfig.SecondaryColor.B - colorConfig.MainColor.B; + return tags + .Select((tag, i) => tag with + { + Color = GetColor(i, tags.Count - 1, colorConfig.MainColor, dr, dg, db), + }) + .ToArray() + .ToResult() + .WithSuccess($"Colored tags with a gradient from {colorConfig.MainColor} to {colorConfig.SecondaryColor}."); + } + + private Color GetColor(int value, int valueRange, Color mainColor, int dr, int dg, int db) + { + var ratio = (double)value / valueRange; + var r = (byte)(mainColor.R + ratio * dr); + var g = (byte)(mainColor.G + ratio * dg); + var b = (byte)(mainColor.B + ratio * db); + return Color.FromArgb(r, g, b); + } +} diff --git a/TagsCloudCreation/TagsDrawingDecorators/ITagsDrawingDecorator.cs b/TagsCloudCreation/TagsDrawingDecorators/ITagsDrawingDecorator.cs new file mode 100644 index 000000000..795bc8f22 --- /dev/null +++ b/TagsCloudCreation/TagsDrawingDecorators/ITagsDrawingDecorator.cs @@ -0,0 +1,8 @@ +using FluentResults; + +namespace TagsCloudCreation.TagsDrawingDecorators; + +public interface ITagsDrawingDecorator +{ + public Result Decorate(IList tags); +} diff --git a/TagsCloudCreation/TagsDrawingDecorators/SingleFontTagsDecorator.cs b/TagsCloudCreation/TagsDrawingDecorators/SingleFontTagsDecorator.cs new file mode 100644 index 000000000..3853e0c8f --- /dev/null +++ b/TagsCloudCreation/TagsDrawingDecorators/SingleFontTagsDecorator.cs @@ -0,0 +1,34 @@ +using FluentResults; +using TagsCloudCreation.Configs; + +namespace TagsCloudCreation.TagsDrawingDecorators; + +public class SingleFontTagsDecorator : ITagsDrawingDecorator +{ + private readonly TagsFontConfig fontConfig; + + public SingleFontTagsDecorator(TagsFontConfig fontConfig) + { + ArgumentNullException.ThrowIfNull(fontConfig); + + this.fontConfig = fontConfig; + } + + public Result Decorate(IList tags) + { + if (tags == null) + { + return Result.Fail("Tags collection is null."); + } + + return tags + .Select(tag => tag with + { + FontName = fontConfig.FontName, + FontStyle = fontConfig.FontStyle, + }) + .ToArray() + .ToResult() + .WithSuccess($"Set tags font to {fontConfig.FontName} {fontConfig.FontStyle}."); + } +} diff --git a/TagsCloudCreation/TagsDrawingDecorators/SingleSolidColorTagsDecorator.cs b/TagsCloudCreation/TagsDrawingDecorators/SingleSolidColorTagsDecorator.cs new file mode 100644 index 000000000..a5ad7a940 --- /dev/null +++ b/TagsCloudCreation/TagsDrawingDecorators/SingleSolidColorTagsDecorator.cs @@ -0,0 +1,31 @@ +using FluentResults; +using System.Drawing; +using TagsCloudCreation.Configs; + +namespace TagsCloudCreation.TagsDrawingDecorators; + +public class SingleSolidColorTagsDecorator : ITagsDrawingDecorator +{ + private readonly TagsColorConfig colorConfig; + + public SingleSolidColorTagsDecorator(TagsColorConfig colorConfig) + { + ArgumentNullException.ThrowIfNull(colorConfig); + + this.colorConfig = colorConfig; + } + + public Result Decorate(IList tags) + { + if (tags == null) + { + return Result.Fail("Tags collection is null."); + } + + return tags + .Select(tag => tag with { Color = colorConfig.MainColor }) + .ToArray() + .ToResult() + .WithSuccess($"Colored tags with {colorConfig.MainColor}."); + } +} diff --git a/TagsCloudCreation/UnplacedTag.cs b/TagsCloudCreation/UnplacedTag.cs new file mode 100644 index 000000000..38785cf15 --- /dev/null +++ b/TagsCloudCreation/UnplacedTag.cs @@ -0,0 +1,5 @@ +using System.Drawing; + +namespace TagsCloudCreation; + +public record UnplacedTag(string Word, Size Size); diff --git a/TagsCloudCreation/WordSizesGetters/FrequencyProportionalWordSizesGetter.cs b/TagsCloudCreation/WordSizesGetters/FrequencyProportionalWordSizesGetter.cs new file mode 100644 index 000000000..afc5de71d --- /dev/null +++ b/TagsCloudCreation/WordSizesGetters/FrequencyProportionalWordSizesGetter.cs @@ -0,0 +1,53 @@ +using FluentResults; +using System.Drawing; +using TagsCloudCreation.Configs; + +namespace TagsCloudCreation.WordSizesGetters; + +public class FrequencyProportionalWordSizesGetter : IWordSizesGetter, IDisposable +{ + private readonly Image emptyImage = new Bitmap(1, 1); + + private readonly WordSizesGetterConfig wordSizesGetterConfig; + private readonly TagsFontConfig tagsFontConfig; + + public FrequencyProportionalWordSizesGetter(WordSizesGetterConfig wordSizesGetterConfig, TagsFontConfig tagsFontConfig) + { + ArgumentNullException.ThrowIfNull(wordSizesGetterConfig); + ArgumentNullException.ThrowIfNull(tagsFontConfig); + + this.wordSizesGetterConfig = wordSizesGetterConfig; + this.tagsFontConfig = tagsFontConfig; + } + + public void Dispose() + { + emptyImage.Dispose(); + } + + public virtual Result GetSizes(IList words) + { + if (words == null) + { + return Result.Fail("Words collection is null."); + } + + return words + .GroupBy(word => word) + .Select(group => (Word: group.Key, Frequency: group.Count())) + .OrderByDescending(x => x.Frequency) + .Select(x => GetSize(x.Word, x.Frequency)) + .ToArray(); + } + + protected UnplacedTag GetSize(string word, int wordFrequency) + { + var height = (int)(wordSizesGetterConfig.MinSize + wordSizesGetterConfig.Scale * (wordFrequency - 1)); + using var wordFont = new Font(tagsFontConfig.FontName, height, tagsFontConfig.FontStyle, GraphicsUnit.Pixel); + using var graphics = Graphics.FromImage(emptyImage); + + var sizeF = graphics.MeasureString(word, wordFont); + var size = new Size((int)Math.Ceiling(sizeF.Width), height); + return new UnplacedTag(word, size); + } +} diff --git a/TagsCloudCreation/WordSizesGetters/IWordSizesGetter.cs b/TagsCloudCreation/WordSizesGetters/IWordSizesGetter.cs new file mode 100644 index 000000000..b06fa0953 --- /dev/null +++ b/TagsCloudCreation/WordSizesGetters/IWordSizesGetter.cs @@ -0,0 +1,8 @@ +using FluentResults; + +namespace TagsCloudCreation.WordSizesGetters; + +public interface IWordSizesGetter +{ + public Result GetSizes(IList words); +} diff --git a/TagsCloudCreation/WordSizesGetters/SmoothFrequencyProportionalWordSizesGetter.cs b/TagsCloudCreation/WordSizesGetters/SmoothFrequencyProportionalWordSizesGetter.cs new file mode 100644 index 000000000..56291e117 --- /dev/null +++ b/TagsCloudCreation/WordSizesGetters/SmoothFrequencyProportionalWordSizesGetter.cs @@ -0,0 +1,31 @@ +using FluentResults; +using TagsCloudCreation.Configs; + +namespace TagsCloudCreation.WordSizesGetters; + +public class SmoothFrequencyProportionalWordSizesGetter : FrequencyProportionalWordSizesGetter +{ + public SmoothFrequencyProportionalWordSizesGetter( + WordSizesGetterConfig wordSizesGetterConfig, + TagsFontConfig tagsFontConfig) + : base(wordSizesGetterConfig, tagsFontConfig) + { + } + + public override Result GetSizes(IList words) + { + if (words == null) + { + return Result.Fail("Words collection is null."); + } + + return words + .GroupBy(word => word) + .Select(group => (Word: group.Key, Frequency: group.Count())) + .GroupBy(x => x.Frequency) + .OrderBy(group => group.Key) + .SelectMany((group, i) => group.Select(x => GetSize(x.Word, i + 1))) + .Reverse() + .ToArray(); + } +} diff --git a/TagsCloudCreation_Tests/TagsCloudCreation_Tests.csproj b/TagsCloudCreation_Tests/TagsCloudCreation_Tests.csproj new file mode 100644 index 000000000..f29ffc81c --- /dev/null +++ b/TagsCloudCreation_Tests/TagsCloudCreation_Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0-windows + enable + enable + + false + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/TagsCloudCreation_Tests/TagsDrawers/TagsDrawerTests.cs b/TagsCloudCreation_Tests/TagsDrawers/TagsDrawerTests.cs new file mode 100644 index 000000000..213a0504e --- /dev/null +++ b/TagsCloudCreation_Tests/TagsDrawers/TagsDrawerTests.cs @@ -0,0 +1,68 @@ +using FluentAssertions; +using FluentResults; +using System.Drawing; +using TagsCloudCreation.Configs; +using TagsCloudCreation.TagsDrawers; + +namespace TagsCloudCreation_Tests.TagsDrawers; + +[TestFixture] +internal class TagsDrawerTests +{ + private static readonly Color backgroundColor = Color.FromArgb(255, 255, 255); + + private TagsColorConfig tagsColorConfig; + private ITagsDrawer tagsDrawer; + + private Result? imageResult; + + [SetUp] + public void SetUp() + { + tagsColorConfig = new TagsColorConfig(default, default, backgroundColor); + tagsDrawer = new TagsDrawer(tagsColorConfig); + } + + [TearDown] + public void TearDown() + { + if (imageResult != null && imageResult.IsSuccess) + { + imageResult.Value?.Dispose(); + } + } + + [Test] + public void Constructor_ThrowsException_WhenTagsColorConfigIsNull() + { + var ctor = () => new TagsDrawer(null!); + ctor.Should().Throw(); + } + + [Test] + public void Draw_Fails_WhenTagsListIsNull() + { + imageResult = tagsDrawer.Draw(null!); + imageResult.IsSuccess.Should().BeFalse(); + } + + [Test] + public void Draw_Returns1x1Image_WhenTagsListIsEmpty() + { + var expectedSize = new Size(1, 1); + + imageResult = tagsDrawer.Draw([]); + + imageResult.IsSuccess.Should().BeTrue(); + imageResult.Value.Size.Should().Be(expectedSize); + } + + [Test] + public void Draw_SetsImageBackgroundColor() + { + imageResult = tagsDrawer.Draw([]); + + imageResult.IsSuccess.Should().BeTrue(); + imageResult.Value.GetPixel(0, 0).Should().Be(backgroundColor); + } +} diff --git a/TagsCloudCreation_Tests/TagsDrawingDecorators/GradientTagsDecoratorTests.cs b/TagsCloudCreation_Tests/TagsDrawingDecorators/GradientTagsDecoratorTests.cs new file mode 100644 index 000000000..32190b70f --- /dev/null +++ b/TagsCloudCreation_Tests/TagsDrawingDecorators/GradientTagsDecoratorTests.cs @@ -0,0 +1,48 @@ +using FakeItEasy; +using FluentAssertions; +using System.Drawing; +using TagsCloudCreation.Configs; +using TagsCloudCreation.TagsDrawingDecorators; + +namespace TagsCloudCreation_Tests.TagsDrawingDecorators; + +[TestFixture] +internal class GradientTagsDecoratorTests : TagsDrawingDecoratorTests +{ + private static readonly Color configMainColor = Color.FromArgb(0, 3, 6); + private static readonly Color configSecondaryColor = Color.FromArgb(3, 3, 3); + + private TagsColorConfig tagsColorConfig; + + [SetUp] + public void SetUp() + { + tagsColorConfig = new TagsColorConfig(configMainColor, configSecondaryColor, default); + tagsDecorator = new GradientTagsDecorator(tagsColorConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenTagsColorConfigIsNull() + { + var ctor = () => new GradientTagsDecorator(null!); + ctor.Should().Throw(); + } + + [Test] + public void Decorate_SetsTagBrushesColorsInGradientByIndex() + { + var expectedColors = new[] + { + Color.FromArgb(0, 3, 6), + Color.FromArgb(1, 3, 5), + Color.FromArgb(2, 3, 4), + Color.FromArgb(3, 3, 3), + }; + + var decoratedTagsResult = tagsDecorator.Decorate(tags); + + decoratedTagsResult.IsSuccess.Should().BeTrue(); + var decoratedTagsColors = decoratedTagsResult.Value.Select(tag => tag.Color); + decoratedTagsColors.Should().BeEquivalentTo(expectedColors, options => options.WithStrictOrdering()); + } +} diff --git a/TagsCloudCreation_Tests/TagsDrawingDecorators/SingleFontTagsDecoratorTests.cs b/TagsCloudCreation_Tests/TagsDrawingDecorators/SingleFontTagsDecoratorTests.cs new file mode 100644 index 000000000..eb335da60 --- /dev/null +++ b/TagsCloudCreation_Tests/TagsDrawingDecorators/SingleFontTagsDecoratorTests.cs @@ -0,0 +1,46 @@ +using FakeItEasy; +using FluentAssertions; +using System.Drawing; +using TagsCloudCreation.Configs; +using TagsCloudCreation.TagsDrawingDecorators; + +namespace TagsCloudCreation_Tests.TagsDrawingDecorators; + +[TestFixture] +internal class SingleFontTagsDecoratorTests : TagsDrawingDecoratorTests +{ + private const string ConfigFontName = "Arial"; + private const FontStyle ConfigFontStyle = FontStyle.Regular; + + private TagsFontConfig tagsFontConfig; + + [SetUp] + public void SetUp() + { + tagsFontConfig = new TagsFontConfig(ConfigFontName, ConfigFontStyle); + tagsDecorator = new SingleFontTagsDecorator(tagsFontConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenTagsFontConfigIsNull() + { + var ctor = () => new SingleFontTagsDecorator(null!); + ctor.Should().Throw(); + } + + [Test] + public void Decorate_SetsTagsFontNameAndFontStyle() + { + var expectedTags = tags + .Select(tag => tag with + { + FontName = ConfigFontName, + FontStyle = ConfigFontStyle, + }); + + var decoratedTags = tagsDecorator.Decorate(tags); + + decoratedTags.IsSuccess.Should().BeTrue(); + decoratedTags.Value.Should().BeEquivalentTo(expectedTags); + } +} diff --git a/TagsCloudCreation_Tests/TagsDrawingDecorators/SingleSolidColorTagsDecoratorTests.cs b/TagsCloudCreation_Tests/TagsDrawingDecorators/SingleSolidColorTagsDecoratorTests.cs new file mode 100644 index 000000000..1bf040c5a --- /dev/null +++ b/TagsCloudCreation_Tests/TagsDrawingDecorators/SingleSolidColorTagsDecoratorTests.cs @@ -0,0 +1,41 @@ +using FakeItEasy; +using FluentAssertions; +using System.Drawing; +using TagsCloudCreation.Configs; +using TagsCloudCreation.TagsDrawingDecorators; + +namespace TagsCloudCreation_Tests.TagsDrawingDecorators; + +[TestFixture] +internal class SingleSolidColorTagsDecoratorTests : TagsDrawingDecoratorTests +{ + private static readonly Color configMainColor = Color.FromArgb(0, 0, 0); + + private TagsColorConfig tagsColorConfig; + + [SetUp] + public void SetUp() + { + tagsColorConfig = new TagsColorConfig(configMainColor, default, default); + tagsDecorator = new SingleSolidColorTagsDecorator(tagsColorConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenTagsColorConfigIsNull() + { + var ctor = () => new SingleSolidColorTagsDecorator(null!); + ctor.Should().Throw(); + } + + [Test] + public void Decorate_SetsTagsBrush() + { + var expectedTags = tags + .Select(tag => tag with { Color = configMainColor }); + + var decoratedTags = tagsDecorator.Decorate(tags); + + decoratedTags.IsSuccess.Should().BeTrue(); + decoratedTags.Value.Should().BeEquivalentTo(expectedTags); + } +} diff --git a/TagsCloudCreation_Tests/TagsDrawingDecorators/TagsDrawingDecoratorTests.cs b/TagsCloudCreation_Tests/TagsDrawingDecorators/TagsDrawingDecoratorTests.cs new file mode 100644 index 000000000..583aacf47 --- /dev/null +++ b/TagsCloudCreation_Tests/TagsDrawingDecorators/TagsDrawingDecoratorTests.cs @@ -0,0 +1,50 @@ +using FluentAssertions; +using System.Drawing; +using TagsCloudCreation; +using TagsCloudCreation.TagsDrawingDecorators; + +namespace TagsCloudCreation_Tests.TagsDrawingDecorators; + +[TestFixture] +internal abstract class TagsDrawingDecoratorTests +{ + protected static readonly TagDrawing[] tags = + [ + new TagDrawing("a", default, default!, default!, default), + new TagDrawing("b", default, default!, "Century", FontStyle.Italic), + new TagDrawing("c", default, Color.White, default!, default), + new TagDrawing("d", default, Color.White, "Century", FontStyle.Italic), + ]; + + protected ITagsDrawingDecorator tagsDecorator = null!; + + [Test] + public void Decorate_Fails_WhenTagsListIsNull() + { + var decoratedTags = tagsDecorator.Decorate(null!); + decoratedTags.IsSuccess.Should().BeFalse(); + } + + [Test] + public void Decorate_CreatesNewListAndTagDrawings() + { + var decoratedTags = tagsDecorator.Decorate(tags); + + decoratedTags.IsSuccess.Should().BeTrue(); + decoratedTags.Value.Should().NotBeSameAs(tags); + decoratedTags.Value.Should() + .AllSatisfy(decoratedTag => tags.Should() + .AllSatisfy(tag => decoratedTag.Should().NotBeSameAs(tag))); + } + + [Test] + public void Decorate_KeepsTagsOrder() + { + var decoratedTags = tagsDecorator.Decorate(tags); + + decoratedTags.IsSuccess.Should().BeTrue(); + decoratedTags.Value.Should().HaveCount(tags.Length); + decoratedTags.Value.Zip(tags).Should() + .AllSatisfy(tagsPair => tagsPair.First.Word.Should().Be(tagsPair.Second.Word)); + } +} diff --git a/TagsCloudCreation_Tests/WordSizesGetters/FrequencyProportionalWordSizesGetterTests.cs b/TagsCloudCreation_Tests/WordSizesGetters/FrequencyProportionalWordSizesGetterTests.cs new file mode 100644 index 000000000..eb80df9e7 --- /dev/null +++ b/TagsCloudCreation_Tests/WordSizesGetters/FrequencyProportionalWordSizesGetterTests.cs @@ -0,0 +1,65 @@ +using FluentAssertions; +using TagsCloudCreation.Configs; +using TagsCloudCreation.WordSizesGetters; + +namespace TagsCloudCreation_Tests.WordSizesGetters; + +[TestFixture] +internal class FrequencyProportionalWordSizesGetterTests : WordSizesGetterTests +{ + [SetUp] + public override void SetUp() + { + base.SetUp(); + + wordSizesGetter = new FrequencyProportionalWordSizesGetter(wordSizesGetterConfig, tagsFontConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenWordSizesGetterConfigIsNull() + { + var ctor = () => new FrequencyProportionalWordSizesGetter(null!, tagsFontConfig); + ctor.Should().Throw(); + } + + [Test] + public void Constructor_ThrowsException_WhenTagsFontConfigIsNull() + { + var ctor = () => new FrequencyProportionalWordSizesGetter(wordSizesGetterConfig, null!); + ctor.Should().Throw(); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(1.5)] + public void GetSizes_ReturnsTags_WithHeightsIncreasedByWordOccurencesNumber(double scale) + { + var words = new[] { "a", "b", "b", "c", "c", "d", "d", "d", "d" }; + wordSizesGetterConfig = wordSizesGetterConfig with { Scale = scale }; + wordSizesGetter = new FrequencyProportionalWordSizesGetter(wordSizesGetterConfig, tagsFontConfig); + + var unplacedTagsResult = wordSizesGetter.GetSizes(words); + unplacedTagsResult.IsSuccess.Should().BeTrue(); + + var aTag = unplacedTagsResult.Value.Single(tag => tag.Word == "a"); + var bTag = unplacedTagsResult.Value.Single(tag => tag.Word == "b"); + var cTag = unplacedTagsResult.Value.Single(tag => tag.Word == "c"); + var dTag = unplacedTagsResult.Value.Single(tag => tag.Word == "d"); + aTag.Size.Height.Should().Be(ConfigMinSize + (int)((words.Count(word => word == "a") - 1) * scale)); + bTag.Size.Height.Should().Be(ConfigMinSize + (int)((words.Count(word => word == "b") - 1) * scale)); + cTag.Size.Height.Should().Be(ConfigMinSize + (int)((words.Count(word => word == "c") - 1) * scale)); + dTag.Size.Height.Should().Be(ConfigMinSize + (int)((words.Count(word => word == "d") - 1) * scale)); + } + + [Test] + public void GetSizes_ReturnsTags_SortedByHeightInDescendingOrder() + { + var words = new[] { "d", "a", "c", "c", "b", "d", "b", "d" }; + + var unplacedTagsResult = wordSizesGetter.GetSizes(words); + unplacedTagsResult.IsSuccess.Should().BeTrue(); + + var actualHeights = unplacedTagsResult.Value.Select(tag => tag.Size.Height); + actualHeights.Should().BeInDescendingOrder(); + } +} diff --git a/TagsCloudCreation_Tests/WordSizesGetters/SmoothFrequencyProportionalWordSizesGetterTests.cs b/TagsCloudCreation_Tests/WordSizesGetters/SmoothFrequencyProportionalWordSizesGetterTests.cs new file mode 100644 index 000000000..3ea6021b2 --- /dev/null +++ b/TagsCloudCreation_Tests/WordSizesGetters/SmoothFrequencyProportionalWordSizesGetterTests.cs @@ -0,0 +1,63 @@ +using FluentAssertions; +using TagsCloudCreation.WordSizesGetters; + +namespace TagsCloudCreation_Tests.WordSizesGetters; + +internal class SmoothFrequencyProportionalWordSizesGetterTests : WordSizesGetterTests +{ + [SetUp] + public override void SetUp() + { + base.SetUp(); + + wordSizesGetter = new SmoothFrequencyProportionalWordSizesGetter(wordSizesGetterConfig, tagsFontConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenWordSizesGetterConfigIsNull() + { + var ctor = () => new SmoothFrequencyProportionalWordSizesGetter(null!, tagsFontConfig); + ctor.Should().Throw(); + } + + [Test] + public void Constructor_ThrowsException_WhenTagsFontConfigIsNull() + { + var ctor = () => new SmoothFrequencyProportionalWordSizesGetter(wordSizesGetterConfig, null!); + ctor.Should().Throw(); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(1.5)] + public void GetSizes_ReturnsTags_WithHeightsIncreasedByWordFrequencyPlacement(double scale) + { + var words = new[] { "a", "b", "b", "c", "c", "d", "d", "d", "d" }; + wordSizesGetterConfig = wordSizesGetterConfig with { Scale = scale }; + wordSizesGetter = new SmoothFrequencyProportionalWordSizesGetter(wordSizesGetterConfig, tagsFontConfig); + + var unplacedTagsResult = wordSizesGetter.GetSizes(words); + unplacedTagsResult.IsSuccess.Should().BeTrue(); + + var aTag = unplacedTagsResult.Value.Single(tag => tag.Word == "a"); + var bTag = unplacedTagsResult.Value.Single(tag => tag.Word == "b"); + var cTag = unplacedTagsResult.Value.Single(tag => tag.Word == "c"); + var dTag = unplacedTagsResult.Value.Single(tag => tag.Word == "d"); + aTag.Size.Height.Should().Be(ConfigMinSize + (int)(0 * scale)); + bTag.Size.Height.Should().Be(ConfigMinSize + (int)(1 * scale)); + cTag.Size.Height.Should().Be(ConfigMinSize + (int)(1 * scale)); + dTag.Size.Height.Should().Be(ConfigMinSize + (int)(2 * scale)); + } + + [Test] + public void GetSizes_ReturnsTags_SortedByHeightInDescendingOrder() + { + var words = new[] { "d", "a", "c", "c", "b", "d", "b", "d", "d" }; + + var unplacedTagsResult = wordSizesGetter.GetSizes(words); + unplacedTagsResult.IsSuccess.Should().BeTrue(); + + var actualHeights = unplacedTagsResult.Value.Select(tag => tag.Size.Height); + actualHeights.Should().BeInDescendingOrder(); + } +} diff --git a/TagsCloudCreation_Tests/WordSizesGetters/WordSizesGetterTests.cs b/TagsCloudCreation_Tests/WordSizesGetters/WordSizesGetterTests.cs new file mode 100644 index 000000000..1bb31ea42 --- /dev/null +++ b/TagsCloudCreation_Tests/WordSizesGetters/WordSizesGetterTests.cs @@ -0,0 +1,45 @@ +using FluentAssertions; +using System.Drawing; +using TagsCloudCreation.Configs; +using TagsCloudCreation.WordSizesGetters; + +namespace TagsCloudCreation_Tests.WordSizesGetters; + +[TestFixture] +internal abstract class WordSizesGetterTests +{ + protected const int ConfigMinSize = 8; + protected const double ConfigScale = 1; + protected const string ConfigFontName = "Arial"; + protected const FontStyle ConfigFontStyle = FontStyle.Regular; + + protected TagsFontConfig tagsFontConfig; + protected WordSizesGetterConfig wordSizesGetterConfig; + protected FrequencyProportionalWordSizesGetter wordSizesGetter = null!; + + [SetUp] + public virtual void SetUp() + { + wordSizesGetterConfig = new WordSizesGetterConfig(ConfigMinSize, ConfigScale); + tagsFontConfig = new TagsFontConfig(ConfigFontName, ConfigFontStyle); + } + + [Test] + public void GetSizes_Fails_WhenWordsListIsNull() + { + var unplacedTagsResult = wordSizesGetter.GetSizes(null!); + unplacedTagsResult.IsSuccess.Should().BeFalse(); + } + + [Test] + public void GetSizes_ReturnsTagWithHeightEqualToConfigMinSize_IfWordOccursOnce() + { + var words = new[] { "a" }; + + var unplacedTagsResult = wordSizesGetter.GetSizes(words); + + unplacedTagsResult.IsSuccess.Should().BeTrue(); + unplacedTagsResult.Value.Should().HaveCount(1); + unplacedTagsResult.Value[0].Size.Height.Should().Be(ConfigMinSize); + } +} diff --git a/WordsFiltration/Configs/WordsSelectionConfig.cs b/WordsFiltration/Configs/WordsSelectionConfig.cs new file mode 100644 index 000000000..ca688e95d --- /dev/null +++ b/WordsFiltration/Configs/WordsSelectionConfig.cs @@ -0,0 +1,3 @@ +namespace WordsFiltration.Configs; + +public record WordsSelectionConfig(string[]? ExcludedWords, PartOfSpeech[]? IncludedPartsOfSpeech); diff --git a/WordsFiltration/PartOfSpeech.cs b/WordsFiltration/PartOfSpeech.cs new file mode 100644 index 000000000..25ff24342 --- /dev/null +++ b/WordsFiltration/PartOfSpeech.cs @@ -0,0 +1,79 @@ +namespace WordsFiltration; + +public enum PartOfSpeech +{ + /// + /// Часть речи не определена. + /// + Unknown = 0, + + /// + /// Adjective, прилагательное. + /// + A = 1, + + /// + /// Adverb, наречие. + /// + ADV = 2, + + /// + /// Рronominal adverb, местоименное наречие. + /// + ADVPRO = 3, + + /// + /// Numeral-adjective, числительное-прилагательное. + /// + ANUM = 4, + + /// + /// Pronoun-adjective, местоимение-прилагательное. + /// + APRO = 5, + + /// + /// Composite part, часть композита - сложного слова. + /// + COM = 6, + + /// + /// Conjunction, союз. + /// + CONJ = 7, + + /// + /// Interjection, междометие. + /// + INTJ = 8, + + /// + /// Numeral, числительное. + /// + NUM = 9, + + /// + /// Particle, частица. + /// + PART = 10, + + /// + /// Pretext, предлог. + /// + PR = 11, + + /// + /// Noun (substantive), существительное. + /// + S = 12, + + /// + /// Pronoun-noun, местоимение-существительное. + /// + SPRO = 13, + + /// + /// Verb, глагол. + /// + V = 14, +} diff --git a/WordsFiltration/TextSplitter.cs b/WordsFiltration/TextSplitter.cs new file mode 100644 index 000000000..ad1065f7d --- /dev/null +++ b/WordsFiltration/TextSplitter.cs @@ -0,0 +1,54 @@ +using System.Text.RegularExpressions; +using FluentResults; +using Microsoft.Extensions.Logging; +using WordsFiltration.WordsSelectors; + +namespace WordsFiltration; + +public class TextSplitter +{ + private static readonly Regex wordSplitRegex = new Regex(@"[\p{P}\s-[-]]+"); + + private readonly IEnumerable wordsSelectors; + + public TextSplitter(IEnumerable wordsSelectors) + { + ArgumentNullException.ThrowIfNull(wordsSelectors); + + this.wordsSelectors = wordsSelectors; + } + + public Result SplitToWords(string text) + { + if (text == null) + { + return Result + .Fail("Text is null.") + .Log(nameof(TextSplitter), null, LogLevel.Error); + } + + text = text.ToLower(); + + var wordsResult = wordSplitRegex + .Split(text) + .Where(word => !string.IsNullOrEmpty(word) && !word.All(ch => ch == '-')) + .ToResult() + .Log(nameof(TextSplitter), "Text was split into words.", LogLevel.Information); + + foreach (var wordsSelector in wordsSelectors) + { + var wordsSelectorName = wordsSelector.GetType().Name; + wordsResult = wordsSelector + .Select(wordsResult.Value) + .LogIfSuccess(wordsSelectorName, null, LogLevel.Information) + .LogIfFailed(wordsSelectorName, null, LogLevel.Error); + + if (wordsResult.IsFailed) + { + return Result.Fail("Unable to split text to words."); + } + } + + return Result.Ok(wordsResult.Value.ToArray()); + } +} diff --git a/WordsFiltration/WordsFiltration.csproj b/WordsFiltration/WordsFiltration.csproj new file mode 100644 index 000000000..a1d203e25 --- /dev/null +++ b/WordsFiltration/WordsFiltration.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + PreserveNewest + + + + diff --git a/WordsFiltration/WordsSelectors/IWordsSelector.cs b/WordsFiltration/WordsSelectors/IWordsSelector.cs new file mode 100644 index 000000000..7615e683c --- /dev/null +++ b/WordsFiltration/WordsSelectors/IWordsSelector.cs @@ -0,0 +1,8 @@ +using FluentResults; + +namespace WordsFiltration.WordsSelectors; + +public interface IWordsSelector +{ + public Result> Select(IEnumerable words); +} diff --git a/WordsFiltration/WordsSelectors/PartsOfSpeechFilter.cs b/WordsFiltration/WordsSelectors/PartsOfSpeechFilter.cs new file mode 100644 index 000000000..150bdcfe4 --- /dev/null +++ b/WordsFiltration/WordsSelectors/PartsOfSpeechFilter.cs @@ -0,0 +1,87 @@ +using FluentResults; +using Microsoft.Extensions.Logging; +using MystemSharp; +using WordsFiltration.Configs; + +namespace WordsFiltration.WordsSelectors; + +public class PartsOfSpeechFilter : IWordsSelector +{ + private readonly WordsSelectionConfig wordsSelectionConfig; + + public PartsOfSpeechFilter(WordsSelectionConfig wordsSelectionConfig) + { + ArgumentNullException.ThrowIfNull(wordsSelectionConfig); + + this.wordsSelectionConfig = wordsSelectionConfig; + } + + public Result> Select(IEnumerable words) + { + if (words == null) + { + return Result + .Fail("Words collection is null."); + } + + var includedPartsOfSpeech = wordsSelectionConfig.IncludedPartsOfSpeech?.ToHashSet(); + + if (includedPartsOfSpeech == null) + { + return words + .ToResult() + .WithSuccess("Continuing without filtering words by parts of speech."); + } + + return words + .Select(word => ( + Word: word, + POS: Result + .Try(() => GetPartOfSpeech(word)) + .LogIfFailed( + nameof(PartsOfSpeechFilter), + $"Unable to determine the part of speech of the word '{word}'.", + LogLevel.Warning))) + .Where(x => x.POS.IsSuccess && includedPartsOfSpeech.Contains(x.POS.Value)) + .Select(x => x.Word) + .ToResult() + .WithSuccess($"Words were filtered by parts of speech: [{string.Join(", ", includedPartsOfSpeech)}]."); + } + + private PartOfSpeech GetPartOfSpeech(string word) + { + var grammarList = new Analyses(word)[0].StemGram; + + if (grammarList.Contains(Grammar.Abbreviation)) + { + return PartOfSpeech.Unknown; + } + + return grammarList + .Select(ToPartOfSpeech) + .Where(pos => pos != PartOfSpeech.Unknown) + .SingleOrDefault(PartOfSpeech.Unknown); + } + + private PartOfSpeech ToPartOfSpeech(Grammar grammar) + { + return grammar switch + { + Grammar.Adjective => PartOfSpeech.A, + Grammar.Adverb => PartOfSpeech.ADV, + Grammar.AdvPronoun => PartOfSpeech.ADVPRO, + Grammar.AdjNumeral => PartOfSpeech.ANUM, + Grammar.AdjPronoun => PartOfSpeech.APRO, + Grammar.Composite => PartOfSpeech.COM, + Grammar.Conjunction => PartOfSpeech.CONJ, + Grammar.Interjunction => PartOfSpeech.INTJ, + Grammar.Numeral => PartOfSpeech.NUM, + Grammar.Particle => PartOfSpeech.PART, + Grammar.Preposition => PartOfSpeech.PR, + Grammar.Substantive => PartOfSpeech.S, + Grammar.SubstPronoun => PartOfSpeech.SPRO, + Grammar.Verb => PartOfSpeech.V, + _ => PartOfSpeech.Unknown, + }; + } +} diff --git a/WordsFiltration/WordsSelectors/WordsFilter.cs b/WordsFiltration/WordsSelectors/WordsFilter.cs new file mode 100644 index 000000000..74c9815e3 --- /dev/null +++ b/WordsFiltration/WordsSelectors/WordsFilter.cs @@ -0,0 +1,41 @@ +using FluentResults; +using WordsFiltration.Configs; + +namespace WordsFiltration.WordsSelectors; + +public class WordsFilter : IWordsSelector +{ + private readonly WordsSelectionConfig wordsSelectionConfig; + + public WordsFilter(WordsSelectionConfig wordsSelectionConfig) + { + ArgumentNullException.ThrowIfNull(wordsSelectionConfig); + + this.wordsSelectionConfig = wordsSelectionConfig; + } + + public Result> Select(IEnumerable words) + { + if (words == null) + { + return Result + .Fail("Words collection is null."); + } + + var excludedWords = wordsSelectionConfig.ExcludedWords + ?.Select(word => word.ToLower()) + .ToHashSet(); + + if (excludedWords == null) + { + return words + .ToResult() + .WithSuccess("Continuing without filtering excluded words."); + } + + return words + .Where(word => !excludedWords.Contains(word)) + .ToResult() + .WithSuccess($"Words were excluded."); + } +} diff --git a/WordsFiltration/WordsSelectors/WordsStemmer.cs b/WordsFiltration/WordsSelectors/WordsStemmer.cs new file mode 100644 index 000000000..7069ee354 --- /dev/null +++ b/WordsFiltration/WordsSelectors/WordsStemmer.cs @@ -0,0 +1,28 @@ +using FluentResults; +using Microsoft.Extensions.Logging; +using MystemSharp; + +namespace WordsFiltration.WordsSelectors; + +public class WordsStemmer : IWordsSelector +{ + public Result> Select(IEnumerable words) + { + if (words == null) + { + return Result + .Fail("Words collection is null."); + } + + return words + .Select(word => Result + .Try( + () => new Analyses(word)[0].Text, + ex => new Error($"Unable to stem word '{word}'. {ex.Message}")) + .LogIfFailed(LogLevel.Warning)) + .Where(stemResult => stemResult.IsSuccess) + .Select(stemResult => stemResult.Value) + .ToResult() + .WithSuccess($"Words were stemmed."); + } +} diff --git a/WordsFiltration/mystem_c_binding.dll b/WordsFiltration/mystem_c_binding.dll new file mode 100644 index 000000000..ab1a29901 Binary files /dev/null and b/WordsFiltration/mystem_c_binding.dll differ diff --git a/WordsFiltration_Tests/TextSplitterTests.cs b/WordsFiltration_Tests/TextSplitterTests.cs new file mode 100644 index 000000000..43ce98345 --- /dev/null +++ b/WordsFiltration_Tests/TextSplitterTests.cs @@ -0,0 +1,97 @@ +using FakeItEasy; +using FluentAssertions; +using FluentResults; +using WordsFiltration; +using WordsFiltration.WordsSelectors; + +namespace WordsFiltration_Tests; + +internal class TextSplitterTests +{ + private TextSplitter textSplitter; + + [SetUp] + public void Setup() + { + textSplitter = new TextSplitter([]); + } + + [Test] + public void Constructor_ThrowsException_WhenWordsSelctorsEnumerableIsNull() + { + var ctor = () => new TextSplitter(null!); + ctor.Should().Throw(); + } + + [Test] + public void SplitToWords_Fails_WhenTextIsNull() + { + var actualWords = textSplitter.SplitToWords(null!); + actualWords.IsSuccess.Should().BeFalse(); + } + + [TestCaseSource(nameof(GetTextWithWhiteSpaceAndPunctuationTestCases))] + public void SplitToWords_SplitsTextByWhiteSpaceAndPunctuation(string text, string[] expectedWords) + { + var actualWords = textSplitter.SplitToWords(text); + + actualWords.IsSuccess.Should().BeTrue(); + actualWords.Value.Should().BeEquivalentTo(expectedWords); + } + + [Test] + public void SplitToWords_DoesNotSplitByDash_WhenDashIsPartOfWord() + { + var text = "- a-b"; + var expectedWords = new[] { "a-b" }; + + var actualWords = textSplitter.SplitToWords(text); + + actualWords.IsSuccess.Should().BeTrue(); + actualWords.Value.Should().BeEquivalentTo(expectedWords); + } + + [Test] + public void SplitToWords_AppliesWordsSelectors() + { + var text = "a b c"; + var expectedWords = new[] { "a12", "b12", "c12" }; + + var wordSelector1 = A.Fake(); + var wordSelector2 = A.Fake(); + var textSplitter = new TextSplitter([wordSelector1, wordSelector2]); + + A.CallTo(() => wordSelector1.Select(null!)) + .WithAnyArguments() + .ReturnsLazily(obj => ((IEnumerable)obj.Arguments[0]!).Select(word => word + "1").ToResult()); + A.CallTo(() => wordSelector2.Select(null!)) + .WithAnyArguments() + .ReturnsLazily(obj => ((IEnumerable)obj.Arguments[0]!).Select(word => word + "2").ToResult()); + + var actualWords = textSplitter.SplitToWords(text); + + actualWords.IsSuccess.Should().BeTrue(); + actualWords.Value.Should().BeEquivalentTo(expectedWords, options => options.WithStrictOrdering()); + A.CallTo(() => wordSelector1.Select(null!)) + .WithAnyArguments() + .MustHaveHappenedOnceExactly(); + A.CallTo(() => wordSelector2.Select(null!)) + .WithAnyArguments() + .MustHaveHappenedOnceExactly(); + } + + private static IEnumerable GetTextWithWhiteSpaceAndPunctuationTestCases() + { + yield return new TestCaseData("", new string[0]); + yield return new TestCaseData(" ", new string[0]); + yield return new TestCaseData("!\"#%&'()*,-./:;?@[\\]_{}§«·»", new string[0]); + yield return new TestCaseData("--", new string[0]); + yield return new TestCaseData("a", new[] { "a" }); + yield return new TestCaseData("a ", new[] { "a" }); + yield return new TestCaseData(" a", new[] { "a" }); + yield return new TestCaseData(",a", new[] { "a" }); + yield return new TestCaseData("a,", new[] { "a" }); + yield return new TestCaseData("a b", new[] { "a", "b" }); + yield return new TestCaseData("a,b", new[] { "a", "b" }); + } +} \ No newline at end of file diff --git a/WordsFiltration_Tests/WordsFiltration_Tests.csproj b/WordsFiltration_Tests/WordsFiltration_Tests.csproj new file mode 100644 index 000000000..68e16bd03 --- /dev/null +++ b/WordsFiltration_Tests/WordsFiltration_Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/WordsFiltration_Tests/WordsSelectors/PartsOfSpeechFilterTests.cs b/WordsFiltration_Tests/WordsSelectors/PartsOfSpeechFilterTests.cs new file mode 100644 index 000000000..97f698fbc --- /dev/null +++ b/WordsFiltration_Tests/WordsSelectors/PartsOfSpeechFilterTests.cs @@ -0,0 +1,35 @@ +using FluentAssertions; +using WordsFiltration; +using WordsFiltration.Configs; +using WordsFiltration.WordsSelectors; + +namespace WordsFiltration_Tests.WordsSelectors; + +internal class PartsOfSpeechFilterTests : WordsSelectorTests +{ + [SetUp] + public void SetUp() + { + var wordsSelectionConfig = new WordsSelectionConfig(null, [PartOfSpeech.A, PartOfSpeech.S, PartOfSpeech.V]); + wordSelector = new PartsOfSpeechFilter(wordsSelectionConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenWordsSelectionConfigIsNull() + { + var ctor = () => new PartsOfSpeechFilter(null!); + ctor.Should().Throw(); + } + + [Test] + public void Select_ReturnsWordsOfIncludedPartsOfSpeech() + { + var words = new[] { "ты", "лодка", "копать", "кто", "громко", "два", "песочный", "123", "абвгде", "abcde" }; + var expectedWords = new[] { "лодка", "копать", "песочный" }; + + var actualWords = wordSelector.Select(words); + + actualWords.IsSuccess.Should().BeTrue(); + actualWords.Value.Should().BeEquivalentTo(expectedWords, options => options.WithStrictOrdering()); + } +} diff --git a/WordsFiltration_Tests/WordsSelectors/WordsFilterTests.cs b/WordsFiltration_Tests/WordsSelectors/WordsFilterTests.cs new file mode 100644 index 000000000..e54d2300d --- /dev/null +++ b/WordsFiltration_Tests/WordsSelectors/WordsFilterTests.cs @@ -0,0 +1,34 @@ +using FluentAssertions; +using WordsFiltration.Configs; +using WordsFiltration.WordsSelectors; + +namespace WordsFiltration_Tests.WordsSelectors; + +internal class WordsFilterTests : WordsSelectorTests +{ + [SetUp] + public void SetUp() + { + var wordsSelectionConfig = new WordsSelectionConfig(["a"], null); + wordSelector = new WordsFilter(wordsSelectionConfig); + } + + [Test] + public void Constructor_ThrowsException_WhenWordsSelectionConfigIsNull() + { + var ctor = () => new WordsFilter(null!); + ctor.Should().Throw(); + } + + [Test] + public void Select_ExcludesWords() + { + var words = new[] { "a", "b", "abc", "aaa" }; + var expectedWords = new[] { "b", "abc", "aaa" }; + + var actualWords = wordSelector.Select(words); + + actualWords.IsSuccess.Should().BeTrue(); + actualWords.Value.Should().BeEquivalentTo(expectedWords, options => options.WithStrictOrdering()); + } +} diff --git a/WordsFiltration_Tests/WordsSelectors/WordsSelectorTests.cs b/WordsFiltration_Tests/WordsSelectors/WordsSelectorTests.cs new file mode 100644 index 000000000..05f54d327 --- /dev/null +++ b/WordsFiltration_Tests/WordsSelectors/WordsSelectorTests.cs @@ -0,0 +1,17 @@ +using FluentAssertions; +using MystemSharp; +using WordsFiltration.WordsSelectors; + +namespace WordsFiltration_Tests.WordsSelectors; + +internal abstract class WordsSelectorTests +{ + protected IWordsSelector wordSelector = null!; + + [Test] + public void Select_Fails_WhenWordsEnumerableIsNull() + { + var actualWords = wordSelector.Select(null!); + actualWords.IsSuccess.Should().BeFalse(); + } +} diff --git a/WordsFiltration_Tests/WordsSelectors/WordsStemmerTests.cs b/WordsFiltration_Tests/WordsSelectors/WordsStemmerTests.cs new file mode 100644 index 000000000..c7b1c8c19 --- /dev/null +++ b/WordsFiltration_Tests/WordsSelectors/WordsStemmerTests.cs @@ -0,0 +1,48 @@ +using FluentAssertions; +using WordsFiltration.WordsSelectors; + +namespace WordsFiltration_Tests.WordsSelectors; + +internal class WordsStemmerTests : WordsSelectorTests +{ + [SetUp] + public void SetUp() + { + wordSelector = new WordsStemmer(); + } + + [TestCaseSource(nameof(GetWordsStemTestCases))] + public void Select_ReturnsWordStems(string word, string expectedWord) + { + var actualWords = wordSelector.Select([word]); + + actualWords.IsSuccess.Should().BeTrue(); + actualWords.Value.Should().BeEquivalentTo(expectedWord); + } + + private static IEnumerable GetWordsStemTestCases() + { + yield return new TestCaseData("цветок", "цветок"); + yield return new TestCaseData("цветы", "цветок"); + yield return new TestCaseData("цветка", "цветок"); + yield return new TestCaseData("цветку", "цветок"); + yield return new TestCaseData("цветов", "цветок"); + yield return new TestCaseData("цветков", "цветок"); + + yield return new TestCaseData("желтый", "желтый"); + yield return new TestCaseData("желтая", "желтый"); + yield return new TestCaseData("желтые", "желтый"); + yield return new TestCaseData("желтого", "желтый"); + yield return new TestCaseData("желтому", "желтый"); + + yield return new TestCaseData("сиять", "сиять"); + yield return new TestCaseData("сияет", "сиять"); + yield return new TestCaseData("сиял", "сиять"); + yield return new TestCaseData("сияющий", "сиять"); + yield return new TestCaseData("сиявший", "сиять"); + + yield return new TestCaseData("я", "я"); + yield return new TestCaseData("меня", "я"); + yield return new TestCaseData("мне", "я"); + } +} diff --git a/fp.sln b/fp.sln index d104ab530..1642d4411 100644 --- a/fp.sln +++ b/fp.sln @@ -1,14 +1,23 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSenderRailway", "FileSenderRailway\FileSenderRailway.csproj", "{D979A1EA-516A-46BC-BE6C-8845CA10853D}" +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudCreation", "TagsCloudCreation\TagsCloudCreation.csproj", "{638F204E-C4C1-4237-8828-1DF1D6F1E66B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorHandling", "ErrorHandling\ErrorHandling.csproj", "{66FAF276-533D-4733-AB2E-A9905D678CF6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudCreation_Tests", "TagsCloudCreation_Tests\TagsCloudCreation_Tests.csproj", "{8F10F223-7A0E-4FE8-A7CF-8BBD529F8776}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{754C1CC8-A8B6-46C6-B35C-8A43B80111A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WordsFiltration", "WordsFiltration\WordsFiltration.csproj", "{7444F308-28D3-43D7-9805-B1F4A8FC40F4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Summator", "Samples\Summator\Summator.csproj", "{C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudConsoleInterface", "TagsCloudConsoleInterface\TagsCloudConsoleInterface.csproj", "{4D8EC25F-51D8-46B8-BFE4-E77E340D5F06}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConwaysGameOfLife", "Samples\ConwaysGameOfLife\ConwaysGameOfLife.csproj", "{4B77EC28-5FB5-4095-B3D7-127F5C488D6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudApp", "TagsCloudApp\TagsCloudApp.csproj", "{29204E71-3793-4288-996E-E08B65A2176E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WordsFiltration_Tests", "WordsFiltration_Tests\WordsFiltration_Tests.csproj", "{F3A72FE9-2D24-4B2A-9D37-31D0D384B182}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RectanglesCloudPositioning", "RectanglesCloudPositioning\RectanglesCloudPositioning.csproj", "{ED571CA9-869D-462E-BF37-292233669627}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudApp_Tests", "TagsCloudApp_Tests\TagsCloudApp_Tests.csproj", "{AE5E83F3-BAA1-4C78-80EB-C2D3A2BF27DB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -16,25 +25,43 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Release|Any CPU.Build.0 = Release|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Release|Any CPU.Build.0 = Release|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Release|Any CPU.Build.0 = Release|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Release|Any CPU.Build.0 = Release|Any CPU + {638F204E-C4C1-4237-8828-1DF1D6F1E66B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {638F204E-C4C1-4237-8828-1DF1D6F1E66B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {638F204E-C4C1-4237-8828-1DF1D6F1E66B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {638F204E-C4C1-4237-8828-1DF1D6F1E66B}.Release|Any CPU.Build.0 = Release|Any CPU + {8F10F223-7A0E-4FE8-A7CF-8BBD529F8776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F10F223-7A0E-4FE8-A7CF-8BBD529F8776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F10F223-7A0E-4FE8-A7CF-8BBD529F8776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F10F223-7A0E-4FE8-A7CF-8BBD529F8776}.Release|Any CPU.Build.0 = Release|Any CPU + {7444F308-28D3-43D7-9805-B1F4A8FC40F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7444F308-28D3-43D7-9805-B1F4A8FC40F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7444F308-28D3-43D7-9805-B1F4A8FC40F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7444F308-28D3-43D7-9805-B1F4A8FC40F4}.Release|Any CPU.Build.0 = Release|Any CPU + {4D8EC25F-51D8-46B8-BFE4-E77E340D5F06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D8EC25F-51D8-46B8-BFE4-E77E340D5F06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D8EC25F-51D8-46B8-BFE4-E77E340D5F06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D8EC25F-51D8-46B8-BFE4-E77E340D5F06}.Release|Any CPU.Build.0 = Release|Any CPU + {29204E71-3793-4288-996E-E08B65A2176E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29204E71-3793-4288-996E-E08B65A2176E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29204E71-3793-4288-996E-E08B65A2176E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29204E71-3793-4288-996E-E08B65A2176E}.Release|Any CPU.Build.0 = Release|Any CPU + {F3A72FE9-2D24-4B2A-9D37-31D0D384B182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3A72FE9-2D24-4B2A-9D37-31D0D384B182}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3A72FE9-2D24-4B2A-9D37-31D0D384B182}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3A72FE9-2D24-4B2A-9D37-31D0D384B182}.Release|Any CPU.Build.0 = Release|Any CPU + {ED571CA9-869D-462E-BF37-292233669627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED571CA9-869D-462E-BF37-292233669627}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED571CA9-869D-462E-BF37-292233669627}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED571CA9-869D-462E-BF37-292233669627}.Release|Any CPU.Build.0 = Release|Any CPU + {AE5E83F3-BAA1-4C78-80EB-C2D3A2BF27DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE5E83F3-BAA1-4C78-80EB-C2D3A2BF27DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE5E83F3-BAA1-4C78-80EB-C2D3A2BF27DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE5E83F3-BAA1-4C78-80EB-C2D3A2BF27DB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1} = {754C1CC8-A8B6-46C6-B35C-8A43B80111A0} - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E} = {754C1CC8-A8B6-46C6-B35C-8A43B80111A0} + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E56E33F6-198B-4C52-A969-BBC53257210E} EndGlobalSection EndGlobal