测试动态变量上的属性是否可用

发布于 2024-09-04 23:08:16 字数 335 浏览 6 评论 0原文

我的情况很简单。在我的代码中的某个地方,我有这样的内容:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

//How to do this?
if (myVariable.MyProperty.Exists)   
//Do stuff

所以,基本上我的问题是如何检查(不引发异常)动态变量上的某个属性是否可用。我可以执行 GetType() 但我宁愿避免这样做,因为我实际上不需要知道对象的类型。我真正想知道的是某个属性(或方法,如果这能让生活更轻松)是否可用。有什么指点吗?

My situation is very simple. Somewhere in my code I have this:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

//How to do this?
if (myVariable.MyProperty.Exists)   
//Do stuff

So, basically my question is how to check (without throwing an exception) that a certain property is available on my dynamic variable. I could do GetType() but I'd rather avoid that since I don't really need to know the type of the object. All that I really want to know is whether a property (or method, if that makes life easier) is available. Any pointers?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

宛菡 2024-09-11 23:08:17

以防万一它对某人有帮助:

如果方法 GetDataThatLooksVerySimilarButNotTheSame() 返回一个 ExpandoObject,您也可以在检查之前转换为 IDictionary

dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}

Just in case it helps someone:

If the method GetDataThatLooksVerySimilarButNotTheSame() returns an ExpandoObject you can also cast to a IDictionary before checking.

dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
心头的小情儿 2024-09-11 23:08:17

两种常见的解决方案包括进行调用并捕获 RuntimeBinderException、使用反射检查调用,或者序列化为文本格式并从那里进行解析。异常的问题在于它们非常慢,因为当构造异常时,当前的调用堆栈被序列化。序列化为 JSON 或类似的东西会产生类似的惩罚。这给我们留下了反射,但只有当底层对象实际上是一个具有真实成员的 POCO 时,它才有效。如果它是字典、COM 对象或外部 Web 服务的动态包装器,那么反射将无济于事。

另一种解决方案是使用 IDynamicMetaObjectProvider 获取 DLR 看到的成员名称。在下面的示例中,我使用静态类 (Dynamic) 来测试 Age 字段并显示它。

class Program
{
    static void Main()
    {
        dynamic x = new ExpandoObject();

        x.Name = "Damian Powell";
        x.Age = "21 (probably)";

        if (Dynamic.HasMember(x, "Age"))
        {
            Console.WriteLine("Age={0}", x.Age);
        }
    }
}

public static class Dynamic
{
    public static bool HasMember(object dynObj, string memberName)
    {
        return GetMemberNames(dynObj).Contains(memberName);
    }

    public static IEnumerable<string> GetMemberNames(object dynObj)
    {
        var metaObjProvider = dynObj as IDynamicMetaObjectProvider;

        if (null == metaObjProvider) throw new InvalidOperationException(
            "The supplied object must be a dynamic object " +
            "(i.e. it must implement IDynamicMetaObjectProvider)"
        );

        var metaObj = metaObjProvider.GetMetaObject(
            Expression.Constant(metaObjProvider)
        );

        var memberNames = metaObj.GetDynamicMemberNames();

        return memberNames;
    }
}

The two common solutions to this include making the call and catching the RuntimeBinderException, using reflection to check for the call, or serialising to a text format and parsing from there. The problem with exceptions is that they are very slow, because when one is constructed, the current call stack is serialised. Serialising to JSON or something analogous incurs a similar penalty. This leaves us with reflection but it only works if the underlying object is actually a POCO with real members on it. If it's a dynamic wrapper around a dictionary, a COM object, or an external web service, then reflection won't help.

Another solution is to use IDynamicMetaObjectProvider to get the member names as the DLR sees them. In the example below, I use a static class (Dynamic) to test for the Age field and display it.

class Program
{
    static void Main()
    {
        dynamic x = new ExpandoObject();

        x.Name = "Damian Powell";
        x.Age = "21 (probably)";

        if (Dynamic.HasMember(x, "Age"))
        {
            Console.WriteLine("Age={0}", x.Age);
        }
    }
}

public static class Dynamic
{
    public static bool HasMember(object dynObj, string memberName)
    {
        return GetMemberNames(dynObj).Contains(memberName);
    }

    public static IEnumerable<string> GetMemberNames(object dynObj)
    {
        var metaObjProvider = dynObj as IDynamicMetaObjectProvider;

        if (null == metaObjProvider) throw new InvalidOperationException(
            "The supplied object must be a dynamic object " +
            "(i.e. it must implement IDynamicMetaObjectProvider)"
        );

        var metaObj = metaObjProvider.GetMetaObject(
            Expression.Constant(metaObjProvider)
        );

        var memberNames = metaObj.GetDynamicMemberNames();

        return memberNames;
    }
}
歌枕肩 2024-09-11 23:08:17

Denis 的回答让我想到了使用 JsonObjects(

标头属性检查器)的另一种解决方案:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).OfType<JProperty>()
                                     .Any(prop => prop.Name == "header");

或者可能更好:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).Property("header") != null;

例如:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;

Denis's answer made me think to another solution using JsonObjects,

a header property checker:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).OfType<JProperty>()
                                     .Any(prop => prop.Name == "header");

or maybe better:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).Property("header") != null;

for example:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
葵雨 2024-09-11 23:08:17

好吧,我在单元测试中遇到了类似的问题。

使用 SharpTestsEx 您可以检查属性是否存在。我用这个测试我的控制器,因为由于 JSON 对象是动态的,有人可能会更改名称并忘记在 javascript 或其他内容中更改它,因此在编写控制器时测试所有属性应该会提高我的安全性。

示例:

dynamic testedObject = new ExpandoObject();
testedObject.MyName = "I am a testing object";

现在,使用 SharTestsEx:

Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow();
Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw();

使用此功能,我使用“Should().NotThrow()”测试所有现有属性。

它可能超出主题,但对某人可能有用。

Well, I faced a similar problem but on unit tests.

Using SharpTestsEx you can check if a property existis. I use this testing my controllers, because since the JSON object is dynamic, someone can change the name and forget to change it in the javascript or something, so testing for all properties when writing the controller should increase my safety.

Example:

dynamic testedObject = new ExpandoObject();
testedObject.MyName = "I am a testing object";

Now, using SharTestsEx:

Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow();
Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw();

Using this, i test all existing properties using "Should().NotThrow()".

It's probably out of topic, but can be usefull for someone.

谷夏 2024-09-11 23:08:17

根据 @karask 的回答,您可以将该函数包装为助手,如下所示:

public static bool HasProperty(ExpandoObject expandoObj,
                               string name)
{
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}

Following on from the answer by @karask, you could wrap the function as a helper like so:

public static bool HasProperty(ExpandoObject expandoObj,
                               string name)
{
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}
雄赳赳气昂昂 2024-09-11 23:08:17

对我来说这有效:

if (IsProperty(() => DynamicObject.MyProperty))
  ; // do stuff



delegate string GetValueDelegate();

private bool IsProperty(GetValueDelegate getValueMethod)
{
    try
    {
        //we're not interesting in the return value.
        //What we need to know is whether an exception occurred or not

        var v = getValueMethod();
        return v != null;
    }
    catch (RuntimeBinderException)
    {
        return false;
    }

    return true;
}

For me this works:

if (IsProperty(() => DynamicObject.MyProperty))
  ; // do stuff



delegate string GetValueDelegate();

private bool IsProperty(GetValueDelegate getValueMethod)
{
    try
    {
        //we're not interesting in the return value.
        //What we need to know is whether an exception occurred or not

        var v = getValueMethod();
        return v != null;
    }
    catch (RuntimeBinderException)
    {
        return false;
    }

    return true;
}
半仙 2024-09-11 23:08:17

如果您控制用作动态的类型,难道您不能为每个属性访问返回一个元组而不是一个值吗?类似...

public class DynamicValue<T>
{
    internal DynamicValue(T value, bool exists)
    {
         Value = value;
         Exists = exists;
    }

    T Value { get; private set; }
    bool Exists { get; private set; }
}

可能是一个幼稚的实现,但如果您每次在内部构造其中一个并返回它而不是实际值,您可以在每个属性访问上检查 Exists ,然后点击 如果存在,则值为 default(T) (并且不相关),如果不存在,则值为 default(T) (且不相关)。

也就是说,我可能缺少一些关于动态如何工作的知识,这可能不是一个可行的建议。

If you control the type being used as dynamic, couldn't you return a tuple instead of a value for every property access? Something like...

public class DynamicValue<T>
{
    internal DynamicValue(T value, bool exists)
    {
         Value = value;
         Exists = exists;
    }

    T Value { get; private set; }
    bool Exists { get; private set; }
}

Possibly a naive implementation, but if you construct one of these internally each time and return that instead of the actual value, you can check Exists on every property access and then hit Value if it does with value being default(T) (and irrelevant) if it doesn't.

That said, I might be missing some knowledge on how dynamic works and this might not be a workable suggestion.

百善笑为先 2024-09-11 23:08:17

如果您的用例是转换 api 响应,仅携带几个字段,您可以使用:

var template = new { address = new { street = "" } };
var response = JsonConvert.DeserializeAnonymousType(await result.Content.ReadAsStringAsync(), template);

string street = response?.address?.street;

If your use case is to convert an api response, carrying about only a few fields, you can use this:

var template = new { address = new { street = "" } };
var response = JsonConvert.DeserializeAnonymousType(await result.Content.ReadAsStringAsync(), template);

string street = response?.address?.street;
时间海 2024-09-11 23:08:17

您可以首先将其转换为 JObject 并使用 .ContainsKey() 函数检查属性是否存在。

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
var jObject = JObject.FromObject(myVariable);

if(jObject.ContainsKey("MyProperty")){
  // "MyProperty" exists
}

JObject 位于 Newtonsoft.Json.Linq 命名空间中

You could first covert it to a JObject and use the .ContainsKey() function to check if a property exists.

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
var jObject = JObject.FromObject(myVariable);

if(jObject.ContainsKey("MyProperty")){
  // "MyProperty" exists
}

JObject is found in the Newtonsoft.Json.Linq namespace

静水深流 2024-09-11 23:08:17

这是另一种方式:

using Newtonsoft.Json.Linq;

internal class DymanicTest
{
    public static string Json = @"{
            ""AED"": 3.672825,
            ""AFN"": 56.982875,
            ""ALL"": 110.252599,
            ""AMD"": 408.222002,
            ""ANG"": 1.78704,
            ""AOA"": 98.192249,
            ""ARS"": 8.44469
}";

    public static void Run()
    {
        dynamic dynamicObject = JObject.Parse(Json);

        foreach (JProperty variable in dynamicObject)
        {
            if (variable.Name == "AMD")
            {
                var value = variable.Value;
            }
        }
    }
}

Here is the other way:

using Newtonsoft.Json.Linq;

internal class DymanicTest
{
    public static string Json = @"{
            ""AED"": 3.672825,
            ""AFN"": 56.982875,
            ""ALL"": 110.252599,
            ""AMD"": 408.222002,
            ""ANG"": 1.78704,
            ""AOA"": 98.192249,
            ""ARS"": 8.44469
}";

    public static void Run()
    {
        dynamic dynamicObject = JObject.Parse(Json);

        foreach (JProperty variable in dynamicObject)
        {
            if (variable.Name == "AMD")
            {
                var value = variable.Value;
            }
        }
    }
}
聚集的泪 2024-09-11 23:08:17

就我而言,我需要检查是否存在具有特定名称的方法,因此我为此使用了一个接口

var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
if (plugin != null && plugin is ICustomPluginAction)
{
    plugin.CustomPluginAction(action);
}

此外,接口可以包含的不仅仅是方法:

接口可以包含方法、属性、事件、索引器或任何
这四种成员类型的组合。

来自:接口(C# 编程指南)

优雅不需要捕获异常或玩反射......

In my case, I needed to check for the existence of a method with a specific name, so I used an interface for that

var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
if (plugin != null && plugin is ICustomPluginAction)
{
    plugin.CustomPluginAction(action);
}

Also, interfaces can contain more than just methods:

Interfaces can contain methods, properties, events, indexers, or any
combination of those four member types.

From: Interfaces (C# Programming Guide)

Elegant and no need to trap exceptions or play with reflexion...

玻璃人 2024-09-11 23:08:17

我知道这确实是旧帖子,但这里有一个在 c# 中使用 dynamic 类型的简单解决方案。

  1. 可以使用简单的反射来枚举直接属性
  2. 或者可以使用object扩展方法
  3. 或使用 GetAsOrDefault 方法获取一个新的强类型对象,如果存在则使用值,如果不存在则使用默认值。
public static class DynamicHelper
{
    private static void Test( )
    {
        dynamic myobj = new
                        {
                            myInt = 1,
                            myArray = new[ ]
                                      {
                                          1, 2.3
                                      },
                            myDict = new
                                     {
                                         myInt = 1
                                     }
                        };

        var myIntOrZero = myobj.GetAsOrDefault< int >( ( Func< int > )( ( ) => myobj.noExist ) );
        int? myNullableInt = GetAs< int >( myobj, ( Func< int > )( ( ) => myobj.myInt ) );

        if( default( int ) != myIntOrZero )
            Console.WriteLine( $"myInt: '{myIntOrZero}'" );

        if( default( int? ) != myNullableInt )
            Console.WriteLine( $"myInt: '{myNullableInt}'" );

        if( DoesPropertyExist( myobj, "myInt" ) )
            Console.WriteLine( $"myInt exists and it is: '{( int )myobj.myInt}'" );
    }

    public static bool DoesPropertyExist( dynamic dyn, string property )
    {
        var t = ( Type )dyn.GetType( );
        var props = t.GetProperties( );
        return props.Any( p => p.Name.Equals( property ) );
    }

    public static object GetAs< T >( dynamic obj, Func< T > lookup )
    {
        try
        {
            var val = lookup( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return null;
    }

    public static T GetAsOrDefault< T >( this object obj, Func< T > test )
    {
        try
        {
            var val = test( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return default( T );
    }
}

I know this is really old post but here is a simple solution to work with dynamic type in c#.

  1. can use simple reflection to enumerate direct properties
  2. or can use the object extention method
  3. or use GetAsOrDefault<int> method to get a new strongly typed object with value if exists or default if not exists.
public static class DynamicHelper
{
    private static void Test( )
    {
        dynamic myobj = new
                        {
                            myInt = 1,
                            myArray = new[ ]
                                      {
                                          1, 2.3
                                      },
                            myDict = new
                                     {
                                         myInt = 1
                                     }
                        };

        var myIntOrZero = myobj.GetAsOrDefault< int >( ( Func< int > )( ( ) => myobj.noExist ) );
        int? myNullableInt = GetAs< int >( myobj, ( Func< int > )( ( ) => myobj.myInt ) );

        if( default( int ) != myIntOrZero )
            Console.WriteLine( $"myInt: '{myIntOrZero}'" );

        if( default( int? ) != myNullableInt )
            Console.WriteLine( $"myInt: '{myNullableInt}'" );

        if( DoesPropertyExist( myobj, "myInt" ) )
            Console.WriteLine( $"myInt exists and it is: '{( int )myobj.myInt}'" );
    }

    public static bool DoesPropertyExist( dynamic dyn, string property )
    {
        var t = ( Type )dyn.GetType( );
        var props = t.GetProperties( );
        return props.Any( p => p.Name.Equals( property ) );
    }

    public static object GetAs< T >( dynamic obj, Func< T > lookup )
    {
        try
        {
            var val = lookup( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return null;
    }

    public static T GetAsOrDefault< T >( this object obj, Func< T > test )
    {
        try
        {
            var val = test( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return default( T );
    }
}
拍不死你 2024-09-11 23:08:17

由于 ExpandoObject 继承了 IDictionary 您可以使用以下检查

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

if (((IDictionary<string, object>)myVariable).ContainsKey("MyProperty"))    
//Do stuff

您可以创建一个实用方法来执行此检查,这将使代码更加清晰和重新-可用

As ExpandoObject inherits the IDictionary<string, object> you can use the following check

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

if (((IDictionary<string, object>)myVariable).ContainsKey("MyProperty"))    
//Do stuff

You can make a utility method to perform this check, that will make the code much cleaner and re-usable

梦里寻她 2024-09-11 23:08:16

我认为,除非您重新实现 C# 编译器中处理动态绑定的方式,否则无法在不尝试访问动态变量的情况下找出它是否具有某个成员。这可能会包含很多猜测,因为根据 C# 规范,它是实现定义的。

因此,您实际上应该尝试访问该成员并捕获异常(如果失败):

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 

I think there is no way to find out whether a dynamic variable has a certain member without trying to access it, unless you re-implemented the way dynamic binding is handled in the C# compiler. Which would probably include a lot of guessing, because it is implementation-defined, according to the C# specification.

So you should actually try to access the member and catch an exception, if it fails:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
心欲静而疯不止 2024-09-11 23:08:16

我想我应该对 Martijn 的答案svick 的回答...

以下程序返回以下结果:

Testing with exception: 2430985 ticks
Testing with reflection: 155570 ticks

void Main()
{
    var random = new Random(Environment.TickCount);

    dynamic test = new Test();

    var sw = new Stopwatch();

    sw.Start();

    for (int i = 0; i < 100000; i++)
    {
        TestWithException(test, FlipCoin(random));
    }

    sw.Stop();

    Console.WriteLine("Testing with exception: " + sw.ElapsedTicks.ToString() + " ticks");

    sw.Restart();

    for (int i = 0; i < 100000; i++)
    {
        TestWithReflection(test, FlipCoin(random));
    }

    sw.Stop();

    Console.WriteLine("Testing with reflection: " + sw.ElapsedTicks.ToString() + " ticks");
}

class Test
{
    public bool Exists { get { return true; } }
}

bool FlipCoin(Random random)
{
    return random.Next(2) == 0;
}

bool TestWithException(dynamic d, bool useExisting)
{
    try
    {
        bool result = useExisting ? d.Exists : d.DoesntExist;
        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

bool TestWithReflection(dynamic d, bool useExisting)
{
    Type type = d.GetType();

    return type.GetProperties().Any(p => p.Name.Equals(useExisting ? "Exists" : "DoesntExist"));
}

因此我建议使用反射。 见下文。


回应 bland 的评论:

比率是 100000 次迭代的 reflection:exception 刻度:

Fails 1/1: - 1:43 ticks
Fails 1/2: - 1:22 ticks
Fails 1/3: - 1:14 ticks
Fails 1/5: - 1:9 ticks
Fails 1/7: - 1:7 ticks
Fails 1/13: - 1:4 ticks
Fails 1/17: - 1:3 ticks
Fails 1/23: - 1:2 ticks
...
Fails 1/43: - 1:2 ticks
Fails 1/47: - 1:1 ticks

...足够公平 - 如果您预计它失败的概率小于 ~1/47,那么就选择例外。


上面假设您每次都运行 GetProperties()。您可以通过在字典或类似内容中缓存每种类型的 GetProperties() 结果来加快该过程。如果您反复检查同一组类型,这可能会有所帮助。

I thought I'd do a comparison of Martijn's answer and svick's answer...

The following program returns the following results:

Testing with exception: 2430985 ticks
Testing with reflection: 155570 ticks

void Main()
{
    var random = new Random(Environment.TickCount);

    dynamic test = new Test();

    var sw = new Stopwatch();

    sw.Start();

    for (int i = 0; i < 100000; i++)
    {
        TestWithException(test, FlipCoin(random));
    }

    sw.Stop();

    Console.WriteLine("Testing with exception: " + sw.ElapsedTicks.ToString() + " ticks");

    sw.Restart();

    for (int i = 0; i < 100000; i++)
    {
        TestWithReflection(test, FlipCoin(random));
    }

    sw.Stop();

    Console.WriteLine("Testing with reflection: " + sw.ElapsedTicks.ToString() + " ticks");
}

class Test
{
    public bool Exists { get { return true; } }
}

bool FlipCoin(Random random)
{
    return random.Next(2) == 0;
}

bool TestWithException(dynamic d, bool useExisting)
{
    try
    {
        bool result = useExisting ? d.Exists : d.DoesntExist;
        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

bool TestWithReflection(dynamic d, bool useExisting)
{
    Type type = d.GetType();

    return type.GetProperties().Any(p => p.Name.Equals(useExisting ? "Exists" : "DoesntExist"));
}

As a result I'd suggest using reflection. See below.


Responding to bland's comment:

Ratios are reflection:exception ticks for 100000 iterations:

Fails 1/1: - 1:43 ticks
Fails 1/2: - 1:22 ticks
Fails 1/3: - 1:14 ticks
Fails 1/5: - 1:9 ticks
Fails 1/7: - 1:7 ticks
Fails 1/13: - 1:4 ticks
Fails 1/17: - 1:3 ticks
Fails 1/23: - 1:2 ticks
...
Fails 1/43: - 1:2 ticks
Fails 1/47: - 1:1 ticks

...fair enough - if you expect it to fail with a probability with less than ~1/47, then go for exception.


The above assumes that you're running GetProperties() each time. You may be able to speed up the process by caching the result of GetProperties() for each type in a dictionary or similar. This may help if you're checking against the same set of types over and again.

做个ˇ局外人 2024-09-11 23:08:16

也许使用反射?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 

Maybe use reflection?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文