如何以有效和快速的方式使用不同的参数屏蔽字符串

How to mask a string using different parameters in an effective and fast way

我想使用不同的参数来屏蔽字符串,例如要屏蔽的字符百分比、屏蔽字符和应用屏蔽的位置(在字符串的开头、中间或结尾) .我想出了一个解决方案,但我认为这不是最好的解决方案。这是我的代码:

public static string MaskChars(this string value, char maskToApply = 'X', int percentToApply = 25, MaskOption maskOptions = MaskOption.InTheMiddleOfString)
    {
        string valueTrimmed = value.Trim();
        int len = valueTrimmed.Length;

        if (len == 0)
            return Empty;
        if (percentToApply >= 100)
            return maskToApply.ToString(CultureInfo.InvariantCulture).Replicate(len);

        var charsToMask = (int)Math.Round((decimal)(percentToApply * len) / 100);
        if (charsToMask == 0)
            charsToMask = 1;

        int top = len - charsToMask;
        int maskCounter = 0;
        var builder = new StringBuilder(len);

        for (int i = 0; i < len; i++)
        {
            if (maskCounter < charsToMask)
            {
                switch (maskOptions)
                {
                    // Apply mask in the middle of the string
                    case MaskOption.InTheMiddleOfString:
                        if (i >= charsToMask && i < top)
                        {
                            builder.Append(maskToApply);
                            maskCounter++;
                        }
                        break;
                    // Apply mask at the begining of the string
                    case MaskOption.AtTheBeginingOfString:
                        if (i < charsToMask)
                        {
                            builder.Append(maskToApply);
                            maskCounter++;
                        }
                        break;
                    // Apply mask at the end of the string
                    case MaskOption.AtTheEndOfString:
                        if (i >= top)
                        {
                            builder.Append(maskToApply);
                            maskCounter++;
                        }
                        break;
                }
            }
            else
            {
                builder.Append(valueTrimmed[i]);
            }
        }

        return builder.ToString();
    }

其中:

public enum MaskOption : byte
{
    AtTheBeginingOfString = 1,
    InTheMiddleOfString = 2,
    AtTheEndOfString = 3
}

Replicate 是一种复制字符串的简单方法

public static string Replicate(this string value, int count)
    {
        if (IsNullOrEmpty(value))
            return Empty;
        if (count <= 0)
            return value;

        var builder = new StringBuilder();
        builder.Append(value);
        for (int i = count; i >= 1; i--)
            builder.Append(value);

        return builder.ToString();
    }

首先,我 运行 您的代码以查看预期的行为是什么,我认为它不正确。这是测试代码和输出:

var testStr = "This is my string to mask characters in!";
Console.WriteLine(testStr.MaskChars('X', 25, Extensions.MaskOption.AtTheBeginingOfString));
Console.WriteLine(testStr.MaskChars('X', 25, Extensions.MaskOption.InTheMiddleOfString));
Console.WriteLine(testStr.MaskChars(maskOptions: Extensions.MaskOption.AtTheEndOfString));

输出

我的印象是字符串应该保持相同的长度,而被屏蔽的字符只会改变字符串中的位置。另外,我不确定你为什么要 trim 字符串(我不会在这个方法中这样做,我会让调用者决定他们是否想先 trim 它),但是我把那部分留在里面了。

下面是我将如何简化代码来做到这一点:

public static string MaskChars(this string input, char maskChar, 
    int percentToApply, MaskOption maskOptions)
{
    // I would remove this. The caller can trim the string first if they want.
    var result = input.Trim(); 

    if (result.Length == 0 || percentToApply < 1) return result;
    if (percentToApply >= 100) return new string(maskChar, result.Length);

    var maskLength = Math.Max((int) Math.Round(percentToApply * result.Length / 100m), 1);
    var mask = new string(maskChar, maskLength);

    switch (maskOptions)
    {
        case MaskOption.AtTheBeginingOfString:
            result = mask + result.Substring(maskLength);
            break;
        case MaskOption.AtTheEndOfString:
            result = result.Substring(0, result.Length - maskLength) + mask;
            break;
        case MaskOption.InTheMiddleOfString:
            var maskStart = (result.Length - maskLength) / 2;
            result = result.Substring(0, maskStart) + mask + 
                result.Substring(maskStart + maskLength);
            break;
    }

    return result;
}

输出

我要做的最后一件事是摆脱方法中的默认参数值,并创建一些重载方法,而不是使用缺少参数的默认值。通过这种方式,用户可以调整所需的值(在您的实现中,如果他们只想更改 MaskOption,则他们必须重新声明其他默认值或像我上面那样使用命名参数):

private static char defaultMaskChar = 'X';
private static MaskOption defaultMaskOption = MaskOption.InTheMiddleOfString;
private static int defaultPercentToApply = 25;

public static string MaskChars(this string input)
{
    return MaskChars(input, defaultMaskChar);
}

public static string MaskChars(this string input, char maskChar)
{
    return MaskChars(input, maskChar, defaultPercentToApply);
}

public static string MaskChars(this string input, int percentToApply)
{
    return MaskChars(input, defaultMaskChar, percentToApply);
}

public static string MaskChars(this string input, MaskOption maskOption)
{
    return MaskChars(input, defaultMaskChar, defaultPercentToApply, maskOption);
}

public static string MaskChars(this string input, char maskChar, int percentToApply)
{
    return MaskChars(input, maskChar, percentToApply, defaultMaskOption);
}

public static string MaskChars(this string input, char maskChar, MaskOption maskOption)
{
    return MaskChars(input, maskChar, defaultPercentToApply, maskOption);
}

public static string MaskChars(this string input, int percentToApply, MaskOption maskOption)
{
    return MaskChars(input, defaultMaskChar, percentToApply, maskOption);
}

您可以尝试另一种方法来做同样的事情。参数更少,更容易测试,也更容易阅读。

请注意,我没有 post 像 Guard 这样的界面和小细节。

   public class StringMask : IStringMask
    {
        /// <summary>
        /// The Mask character
        /// </summary>
        private readonly char MaskCharacter;

        /// <summary>
        /// The instance
        /// </summary>
        private readonly string Instance;

        /// <summary>
        /// The Mask
        /// </summary>
        private BitArray Mask;

        /// <summary>
        /// Initializes a new instance of the <see cref="StringMask"/> class.
        /// </summary>
        /// <param name="instance">The string you would like to mask.</param>
        /// <param name="maskCharacter">The Mask character.</param>
        public StringMask(string instance, char maskCharacter)
        {
            MaskCharacter = maskCharacter;
            Instance = instance;
            Mask = new BitArray(instance.Length, false);
        }



        /// <summary>
        /// Shows the first [number] of characters and masks the rest.
        /// </summary>
        /// <param name="number">The number of the characters to show.</param>
        /// <returns>IStringMask.</returns>
        public IStringMask ShowFirst(int number)
        {
            Validate(number);

            for (int i = 0; i < number; i++)
            {
                Mask[i] = true;
            }
            return this;
        }

        /// <summary>
        /// Shows the last [number] of characters and masks the rest.
        /// </summary>
        /// <param name="number">The number of the characters to show.</param>
        /// <returns>IStringMask.</returns>
        public IStringMask ShowLast(int number)
        {
            Validate(number);

            for (int i = 0; i < number; i++)
            {
                Mask[Instance.Length - i - 1] = true;
            }
            return this;

        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
        public override string ToString()
        {
            var sb = new StringBuilder();
            for (int i = 0; i < Instance.Length; i++)
            {
                if (Mask[i])
                    sb.Append(Instance[i]);
                else
                    sb.Append(MaskCharacter);
            }

            return sb.ToString();
        }

        private void Validate(int number)
        {
            Guard.IsBetweenExclusive(number, 0, Instance.Length, nameof(number));
        }
    }

虽然你可以在这里看到测试:

 [TestFixture]
    internal class MaskTests
    {
        private string input = "40770058698999513265";
        private char maskChar = 'X';
        private IStringMask mask;

        [SetUp]
        public void Initiate()
        {
            mask = new StringMask(input, maskChar);
        }

        [Test]
        public void MaskShowLast()
        {
            var output = mask.ShowLast(10);
            Console.WriteLine(output);
            Assert.AreEqual("XXXXXXXXXX8999513265", output.ToString());
        }



        [Test]
        public void MaskInTheMiddle()
        {
            var output = mask.ShowLast(5).ShowFirst(5);
            Console.WriteLine(output);
            Assert.AreEqual("40770XXXXXXXXXX13265", output.ToString());
        }

        [Test]
        public void MaskInTheMiddleTooShort()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => mask.ShowLast(0).ShowFirst(0));
        }

        [Test]
        public void MaskInTheMiddleTooLong()
        {
            Assert.Throws<ArgumentOutOfRangeException>(()=> mask.ShowLast(500).ShowFirst(500));
        }

        [Test]
        public void MaskAtTheEnd()
        {
            var output = mask.ShowFirst(10);
            Console.WriteLine(output);
            Assert.AreEqual("4077005869XXXXXXXXXX", output.ToString());

        }
    }