RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1215963
Accepted
Bulson
Bulson
Asked:2021-12-09 01:11:48 +0000 UTC2021-12-09 01:11:48 +0000 UTC 2021-12-09 01:11:48 +0000 UTC

FluentBuilder 用于不可变类

  • 772

总的来说,在这个问题之后,我决定写一个例子,但不幸的是我没有掌握它😢。也许你们中的一个可以帮我完成它。

我们有这样一类人

class Person
{
    public Person(string firstName, string lastName,
        Address address, Employment employment)
    {
        FirstName = firstName;
        LastName = lastName;
        Address = address;
        Employment = employment;
    }

    public string FirstName { get; }
    public string LastName { get; }
    public Address Address { get; }
    public Employment Employment { get; }

    public override string ToString()
    {
        return $"{FirstName} {LastName}: "
            + Environment.NewLine +
            $"{Address}"
            + Environment.NewLine +
            $"{Employment}";
    }
}

与同伴课程

class Address
{
    public string City { get; set; }
    public int PostCode { get; set; }

    public override string ToString()
    {
        return $"Адрес: {City}-{PostCode} ";
    }
}

class Employment
{
    public string CompanyName { get; set; }
    public string Position { get; set; }
    public int AnnualIncome { get; set; }

    public override string ToString()
    {
        return $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}";
    }
}

独立设法只编写这样一个构建器

class PersonBuilder
{
    //аккумулятор названий свойств и их значений
    private readonly Dictionary<string, object> _propertiesToBuild;

    public PersonBuilder()
    {
        _propertiesToBuild = new Dictionary<string, object>();
    }

    //запоминание в словаре названия свойства и его значения
    public PersonBuilder Set<T>(Expression<Func<Person, T>> expression, T value)
    {
        var propertyName = ((MemberExpression)expression.Body).Member.Name;
        _propertiesToBuild.Add(propertyName, value);
        return this;
    }

    public T Include<T>(Expression<Func<Person, T>> expression) 
    {
        var propertyName = ((MemberExpression)expression.Body).Member.Name;
        if (propertyName == nameof(Person.Address))
        {
            var result = new Address();
            return (T)(object)result;
        }
        else
        {
            var result = new Employment();
            return (T)(object)result;
        }
    }

    //создание экземпляра на основе значений свойств из словаря
    public Person Build()
    {
        return new Person
        (
            firstName: GetPropertyValue<string>(nameof(Person.FirstName), "Неизвестно"),
            lastName: GetPropertyValue<string>(nameof(Person.LastName), "Неизвестно"),
            address: new Address(),
            employment: new Employment()
        );
    }

    private T GetPropertyValue<T>(string propertyName, T defaultValue)
    {
        return _propertiesToBuild.TryGetValue(propertyName, out var value) ? (T)value : defaultValue;
    }
}

虽然它是这样工作的

var person = new PersonBuilder()
                           .Set(p => p.FirstName, "Андрей")
                           .Set(p => p.LastName, "Иванов")
                           .Build();

或者

Address address = new PersonBuilder()
                           .Set(p => p.FirstName, "Андрей")
                           .Set(p => p.LastName, "Иванов")
                           .Include(p => p.Address);

或者像这样

Employment employment = new PersonBuilder()
                           .Set(p => p.FirstName, "Андрей")
                           .Set(p => p.LastName, "Иванов")
                           .Include(p => p.Employment);

我希望结果是这样的

Person person = new PersonBuilder()
                           .Set(p => p.FirstName, "Андрей")
                           .Set(p => p.LastName, "Иванов")
                           .Include(p => p.Address)
                           .Set(a => a.City, "Москва")
                           .Include(p => p)
                           .Include(p => p.Employment)
                           .Set(e => e.Position, "инженер")
                           .Include(p => p)
                           .Build();

PS结果版本在这里,谢谢@Vasek

c#
  • 4 4 个回答
  • 10 Views

4 个回答

  • Voted
  1. VladD
    2021-12-10T04:16:42Z2021-12-10T04:16:42Z

    В качестве пропаганды всего нового и хорошего напомню, что immutable + builder из коробки поддерживается 9-ой версией C#.

    Добавляем в .csproj <LangVersion>9.0</LangVersion>, и ваш пример можно магически записать следующим образом:

    record Person
    {
        public string FirstName { get; init; }
        public string LastName { get; init; }
        public Address Address { get; init; }
        public Employment Employment { get; init; }
        public override string ToString() =>
            $"{FirstName} {LastName}: \n{Address}\n{Employment}";
    }
    
    record Address
    {
        public string City { get; init; }
        public int PostCode { get; init; }
        public override string ToString() =>
            $"Адрес: {City}-{PostCode}";
    }
    
    record Employment
    {
        public string CompanyName { get; init; }
        public string Position { get; init; }
        public int AnnualIncome { get; init; }
        public override string ToString() =>
            $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}";
    }
    

    А код, использующий всё это, запишется так:

    var person =
        new Person()
            with { FirstName = "Андрей" }
            with { LastName = "Иванов" }
            with { Address = new Address()
                                 with { City = "Мюнхен" }
                                 with { PostCode = 80000 } }
            with { Employment = new Employment()
                                 with { CompanyName = "Google" }
                                 with { Position = "CEO" }
                                 with { AnnualIncome = 100500 } };
    

    Всё!

    • 13
  2. aepot
    2021-12-09T03:35:49Z2021-12-09T03:35:49Z

    这样的解决方案怎么样?

    UPD:发现了一个错误。如果同一类型在树中多次出现,它将不起作用。例如,如果你Person有Address WorkAddress和Address HomeAddress。我会考虑如何破解它。

    值得在这里添加防御性编程,例如,必须隐藏设计器,并通过反射拉取。可能是他没有考虑到。

    稍微改了一下数据

    class Person
    {
        public string FirstName { get; }
        public string LastName { get; }
        public Address Address { get; }
        public Employment Employment { get; }
    
        public override string ToString()
        {
            return $"{FirstName} {LastName}: "
                + Environment.NewLine +
                $"{Address}"
                + Environment.NewLine +
                $"{Employment}";
        }
    }
    
    class Address
    {
        public string City { get; }
        public int PostCode { get; }
    
        public override string ToString()
        {
            return $"Адрес: {City}-{PostCode} ";
        }
    }
    
    class Employment
    {
        public string CompanyName { get; }
        public string Position { get; }
        public int AnnualIncome { get; }
    
        public override string ToString()
        {
            return $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}";
        }
    }
    

    这就是发生的事情

    class FluentBuilder<T>
    {
        private readonly Dictionary<Type, Dictionary<string, object>> _container;
    
        private Dictionary<string, object> _properties
        {
            get
            {
                if (!_container.TryGetValue(typeof(T), out var properties))
                {
                    properties = new Dictionary<string, object>();
                    _container[typeof(T)] = properties;
                }
                return properties;
            }
        }
    
        public FluentBuilder()
            => _container = new Dictionary<Type, Dictionary<string, object>>();
    
        public FluentBuilder(Dictionary<Type, Dictionary<string, object>> props)
            => _container = props;
    
        public FluentBuilder<Tprop> As<Tprop>() where Tprop : class
        {
            return new FluentBuilder<Tprop>(_container);
        }
    
        public FluentBuilder<T> Set<Tprop>(Expression<Func<T, Tprop>> expression, Tprop value)
        {
            var propertyName = ((MemberExpression)expression.Body).Member.Name;
            _properties.Add(propertyName, value);
            return this;
        }
    
        public FluentBuilder<Tprop> Include<Tprop>(Expression<Func<T, Tprop>> expression) where Tprop : class
        {
            var propertyName = ((MemberExpression)expression.Body).Member.Name;
            _properties.Add(propertyName, _properties);
            return As<Tprop>();
        }
    
        public T Build()
        {
            // для отладки, можно убрать
            foreach (Type t in _container.Keys)
                foreach (string s in _container[t].Keys)
                    Console.WriteLine($"[{t.Name}] {s}");
    
            return (T)GetInstance(typeof(T));
        }
    
        private FieldInfo GetBackingField(Type type, string propertyName) 
            => type.GetField($"<{propertyName}>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance) 
            ?? throw new NullReferenceException($"Auto properties only allowed. Property: {type.Name}.{propertyName}");
    
        private object GetInstance(Type type)
        {
            object result = Activator.CreateInstance(type);
            Dictionary<string, object> props = _container[type];
            foreach (PropertyInfo p in type.GetProperties())
                if (props.TryGetValue(p.Name, out object v))
                {
                    GetBackingField(result.GetType(), p.Name).SetValue(result, v == props ? GetInstance(p.PropertyType) : v);
                }
            return result;
        }
    }
    

    用法

    Person person = new FluentBuilder<Person>()
                    .Set(p => p.FirstName, "Андрей")
                    .Set(p => p.LastName, "Иванов")
                    .Include(p => p.Address)
                    .Set(a => a.City, "Москва")
                    .As<Person>()
                    .Include(p => p.Employment)
                    .Set(e => e.Position, "инженер")
                    .As<Person>()
                    .Build();
    Console.WriteLine(person);
    

    控制台输出

    Андрей Иванов:
    Адрес: Москва-0
    Работа: , должность: инженер, зарплата: 0
    
    • 10
  3. Best Answer
    Vasek
    2021-12-09T03:35:07Z2021-12-09T03:35:07Z
    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Address Address { get; set; }
        public Employment Employment { get; set; }
    }
    
    class Address
    {
        public string City { get; set; }
        public int PostCode { get; set; }
    }
    
    class Employment
    {
        public string CompanyName { get; set; }
        public string Position { get; set; }
        public int AnnualIncome { get; set; }
    }
    
    class Builder<T>
    {
        public Builder Root { get; }
        public Dictionary<string, object> Props { get; }
    
        public Builder(Builder root)
        {
            Root = root;
            Props = new Dictionary<string, object>();
        }
    
        public Builder<T> Set<TProp>(Expression<Func<T, TProp>> expression, TProp value)
        {
            var propertyName = ((MemberExpression)expression.Body).Member.Name;
            Props.Add(propertyName, value);
            return this;
        }
    
        public virtual Builder<TProp> Include<TProp>(Expression<Func<Person, TProp>> expression)
        {
            return Root.Include(expression);
        }
    
        public TProp GetPropertyValue<TProp>(string propertyName, TProp defaultValue)
        {
            return Props.TryGetValue(propertyName, out var value) ? (TProp)value : defaultValue;
        }
    
        public virtual Person Build()
        {
            return Root.Build();
        }
    }
    
    class Builder : Builder<Person>
    {
        private Builder<Address> _addressBuilder;
        private Builder<Employment> _employmentBuilder;
    
        public Builder()
            : base(null)
        {
            _addressBuilder = new Builder<Address>(this);
            _employmentBuilder = new Builder<Employment>(this);
        }
    
        public override Builder<TProp> Include<TProp>(Expression<Func<Person, TProp>> expression)
        {
            if (expression.Body is MemberExpression memberExpression) {
                var propName = memberExpression.Member.Name;
                return propName switch {
                    nameof(Person.Address) => _addressBuilder as Builder<TProp>,
                    nameof(Person.Employment) => _employmentBuilder as Builder<TProp>,
                    _ => throw new Exception("lolo")
                };
            }
    
            if (expression.Body is ParameterExpression parameterExpression && parameterExpression.Type == typeof(Person)) {
                return this as Builder<TProp>;
            }
    
            throw new Exception("lolo");
        }
    
        public override Person Build()
        {
            return new Person() {
                FirstName = GetPropertyValue<string>(nameof(Person.FirstName), "!FirstName!"),
                LastName = GetPropertyValue<string>(nameof(Person.LastName), "!LastName!"),
                Address = new Address() {
                    City = _addressBuilder.GetPropertyValue<string>(nameof(Address.City), "!City!"),
                    PostCode = _addressBuilder.GetPropertyValue<int>(nameof(Address.PostCode), -1),
                },
                Employment = new Employment() {
                    CompanyName = _employmentBuilder.GetPropertyValue<string>(nameof(Employment.CompanyName), "!CompanyName!"),
                    Position = _employmentBuilder.GetPropertyValue<string>(nameof(Employment.Position), "!Position!"),
                    AnnualIncome = _employmentBuilder.GetPropertyValue<int>(nameof(Employment.AnnualIncome), -1),
                }
            };
        }
    }
    
    static void Main(string[] args)
    {
        var builder = new Builder()
            .Set(p => p.FirstName, "1")
            .Include(p => p.Address)
            .Set(a => a.City, "3")
            .Set(a => a.PostCode, 123)
            .Include(p => p.Employment)
            .Set(e => e.AnnualIncome, 5)
            .Set(e => e.Position, "6")
            .Include(p => p)
            .Set(p => p.LastName, "2")
            ;
    
        var person = builder.Build();   
    }
    

    Ps: 我想出了这个。从它是必要的事实出发,尽可能简单。我的表达能力不是很强,所以可能有些地方不太对劲,但我认为这个想法会很清楚

    • 8
  4. MSDN.WhiteKnight
    2021-12-09T17:26:10Z2021-12-09T17:26:10Z

    我希望结果是这样的......

    我会批评问题本身的陈述。

    1. PersonBuilder 的含义是什么?这是某种人工实体,实际上是不可变类的可变对应物,需要为每个此类复制。以统一的方式构建对象不是更好吗?

    2. .Set(p => p.FirstName, "Андрей") - использование лямбда-выражения ни с того, ни с сего, хотя анонимная функция тут вообще не вызывается. Почему не .Set("FirstName", "Андрей")?

    3. .Include(p => p) - вообще не понятно, что такое. Как включить самого себя? По логике, происходит переход к родительскому объекту, так что лучше бы создать отдельный метод "ToParent"

    Я бы сделал это так. Допустим, мы принимаем соглашение, что у иммутабельного класса имена get-only свойств начинаются с большой буквы. Параметры конструктора соответствуют по типу его свойствам, а имя параметра начинается путем замены первой буквы имени свойства на такую же маленькую букву. Создадим такой класс:

    public class ImmutableEntity
    {
        static Dictionary<Type, Delegate[]> gettersCache = new Dictionary<Type, Delegate[]>();
        static Dictionary<Type, Delegate> constrCache = new Dictionary<Type, Delegate>();
    
        ImmutableEntity parent;//родительский объект, если этот объект - значение свойства другого объекта
        string parentProperty;//свойство родительского объекта
        
        Delegate[] getters;
        Delegate constr;
    
        protected ImmutableEntity()
        {
            if (gettersCache.ContainsKey(this.GetType()))
            {
                constr = constrCache[this.GetType()];
                getters = gettersCache[this.GetType()];
            }
            else
            {
                PropertyInfo p;
                //получаем первый конструктор, имеющий параметры
                ConstructorInfo ci = this.GetType().GetConstructors().
                    Where(x => x.GetParameters().Length > 0).First();
                ParameterInfo[] p_arr;
                p_arr = ci.GetParameters();
    
                //получаем массив делегатов для получения значений свойств
                ParameterExpression[] expressions = new ParameterExpression[p_arr.Length];
                this.getters = new Delegate[p_arr.Length];
    
                for (int i = 0; i < p_arr.Length; i++)
                {
                    //получаем имя свойства
                    string propname = p_arr[i].Name;
                    propname = Char.ToUpper(propname[0]).ToString() + propname.Substring(1);
    
                    //создаем делегат
                    p = this.GetType().GetProperty(
                     propname, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
                    );
    
                    MethodInfo mb = p.GetGetMethod();
                    Type delegateType = typeof(Func<,>).MakeGenericType(this.GetType(), p.PropertyType);
                    getters[i] = mb.CreateDelegate(delegateType);
    
                    //создаем выражение для параметра
                    expressions[i] = Expression.Parameter(p.PropertyType);
                }
    
                //создаем делегат для вызова конструктора
                LambdaExpression expr = Expression.Lambda(Expression.New(ci, expressions), expressions);
                constr = expr.Compile();
    
                //сохраняем делегаты, чтобы не пересоздавать для каждого объекта одного типа
                gettersCache[this.GetType()] = getters;
                constrCache[this.GetType()] = constr;
            }
        }
        
        public ImmutableEntity Set(string prop, object val)
        {
            //получаем имя параметра из имени свойства
            string pname = prop;
            pname = Char.ToLower(pname[0]).ToString() + pname.Substring(1);
    
            //получаем первый конструктор, имеющий параметры
            ConstructorInfo ci = this.GetType().GetConstructors().
                Where(x => x.GetParameters().Length > 0).First();
    
            //формируем массив значений параметров
            ParameterInfo[] p_arr;
            p_arr = ci.GetParameters();
            object[] pars = new object[p_arr.Length];
    
            for (int i = 0; i < p_arr.Length; i++)
            {
                if (p_arr[i].Name == pname) 
                {
                    //свойство, которое изменилось
                    pars[i] = val; 
                }
                else
                {
                    pars[i] = this.getters[i].DynamicInvoke(this);
                }
            }
    
            //создаем измененный объект
            ImmutableEntity ret=(ImmutableEntity)constr.DynamicInvoke(pars);
            ret.parent = this.parent;
            ret.parentProperty = this.parentProperty;
            return ret;
        }
    
        public static object GetDefault(Type t)
        {
            if (t.IsValueType) return Activator.CreateInstance(t);
            else return null;
        }
    
        public ImmutableEntity Include(string prop)
        {
            //получаем имя параметра из имени свойства
            string pname = prop;
            pname = Char.ToLower(pname[0]).ToString() + pname.Substring(1);
    
            //получаем первый конструктор, имеющий параметры
            ConstructorInfo ci = this.GetType().GetConstructors().
                Where(x => x.GetParameters().Length > 0).First();
    
            //формируем массив значений параметров
            ParameterInfo[] p_arr;
            p_arr = ci.GetParameters();
            object[] pars = new object[p_arr.Length];
            ImmutableEntity included =null;
    
            for (int i = 0; i < p_arr.Length; i++)
            {
                if (p_arr[i].Name == pname)
                {
                    //свойство для включения
                    included = this.getters[i].DynamicInvoke(this) as ImmutableEntity;
    
                    if (included == null)
                    {
                        included = New(p_arr[i].ParameterType);
                        included.parentProperty = prop;
                    }
    
                    pars[i] = included;
                }
                else
                {
                    pars[i] = this.getters[i].DynamicInvoke(this);
                }
            }
    
            //включаем объект в родительский
            included.parent = (ImmutableEntity)constr.DynamicInvoke(pars);
            return included;
        }
    
        public ImmutableEntity ToParent()
        {
            if (this.parent == null) return null;
    
            return this.parent.Set(this.parentProperty, this);
        }
    
        public static T New<T>() where T:ImmutableEntity
        {
            return (T)New(typeof(T));
        }
    
        public static ImmutableEntity New(Type t)
        {
            //создаем пустой объект указанного типа
            ConstructorInfo ci = t.GetConstructors().
                Where(x => x.GetParameters().Length > 0).First();
            ParameterInfo[] p_arr = ci.GetParameters();
            object[] pars = new object[p_arr.Length];
    
            for (int i = 0; i < p_arr.Length; i++)
            {
                pars[i] = GetDefault(p_arr[i].ParameterType);
            }
    
            return (ImmutableEntity)ci.Invoke(pars);
        }
    }
    

    Его суть в использовании выражений для вызова конструктора объекта и получения значений свойств. Так можно строить любой объект, который удовлетворяет нашим соглашениям.

    Сделаем иммутабельные классы производными от него:

    public class Person: ImmutableEntity
    {
        public Person(string firstName, string lastName,
        Address address, Employment employment)
        {
            FirstName = firstName;
            LastName = lastName;
            Address = address;
            Employment = employment;
        }
    
        public string FirstName { get; }
        public string LastName { get; }
        public Address Address { get; }
        public Employment Employment { get; }
    
        public override string ToString()
        {
            return $"{FirstName} {LastName}: "
                + Environment.NewLine +
                $"{Address}"
                + Environment.NewLine +
                $"{Employment}";
        }
    }
    
    public class Address : ImmutableEntity
    {
        public Address(string city, int postCode)
        {
            this.City = city;
            this.PostCode = postCode;
        }
    
        public string City { get; }
        public int PostCode { get; }
    
        public override string ToString()
        {
            return $"Адрес: {City}-{PostCode} ";
        }
    }
    
    public class Employment : ImmutableEntity
    {
        public Employment(string companyName, string position,int annualIncome)
        {
            this.CompanyName = companyName;
            this.Position = position;
            this.AnnualIncome = annualIncome;
        }
    
        public string CompanyName { get;  }
        public string Position { get;  }
        public int AnnualIncome { get;  }
    
        public override string ToString()
        {
            return $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}";
        }
    }
    

    Тогда использовать можно так:

    var person = (Person)ImmutableEntity.New<Person>().
        Set(nameof(Person.FirstName), "Ivan").
        Set(nameof(Person.LastName), "Ivanov").
        Include(nameof(Person.Address)).
        Set(nameof(Address.City), "Chelyabinsk").
        ToParent().
        Include(nameof(Person.Employment)).
        Set(nameof(Employment.Position), "Engineer").
        ToParent();
    
    • 8

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5