Kiểm tra nếu một thuộc tính có sẵn trên một biến động


225

Hoàn cảnh của tôi rất đơn giản. Ở đâu đó trong mã của tôi, tôi có cái này:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Vì vậy, về cơ bản câu hỏi của tôi là làm thế nào để kiểm tra (mà không đưa ra một ngoại lệ) rằng một thuộc tính nhất định có sẵn trên biến động của tôi. Tôi có thể làm GetType()nhưng tôi muốn tránh điều đó vì tôi không thực sự cần biết loại đối tượng. Tất cả những gì tôi thực sự muốn biết là liệu một tài sản (hoặc phương pháp, nếu điều đó làm cho cuộc sống dễ dàng hơn) có sẵn hay không. Bất kỳ con trỏ?


1
Có một vài gợi ý ở đây: stackoverflow.com/questions/2985161/ Khăn - nhưng cho đến nay vẫn chưa có câu trả lời nào được chấp nhận.
Andrew Anderson

cảm ơn, tôi có thể xem làm thế nào để tạo ra một trong những giải pháp, tôi đã tự hỏi liệu có bất cứ điều gì tôi đang bỏ lỡ không
roundcrisis

Câu trả lời:


159

Tôi nghĩ rằng không có cách nào để tìm hiểu xem một dynamicbiến có một thành viên nào đó mà không cố gắng truy cập vào nó hay không, trừ khi bạn thực hiện lại cách xử lý ràng buộc động trong trình biên dịch C #. Mà có lẽ sẽ bao gồm rất nhiều phỏng đoán, bởi vì nó được xác định theo triển khai, theo đặc tả C #.

Vì vậy, bạn thực sự nên cố gắng truy cập thành viên và bắt ngoại lệ, nếu thất bại:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

2
Tôi sẽ đánh dấu đây là câu trả lời đã quá lâu, nó dường như là câu trả lời hay nhất
roundcrisis


20
@ Managedrymason Nếu bạn muốn truyền IDictionaryvà làm việc với nó, chỉ hoạt động trên ExpandoObject, nó sẽ không hoạt động trên bất kỳ dynamicđối tượng nào khác .
Svick

5
RuntimeBinderExceptionlà trong Microsoft.CSharp.RuntimeBinderkhông gian tên.
DavidRR

8
Tôi vẫn cảm thấy như sử dụng try / Catch thay vì if / other là thực tiễn tồi nói chung bất kể chi tiết cụ thể của kịch bản này.
Alexander Ryan Baggett

74

Tôi nghĩ tôi sẽ làm một phép so sánh câu trả lời của Martijncâu trả lời của Svick ...

Chương trình sau đây trả về kết quả sau:

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"));
}

Kết quả là tôi đề nghị sử dụng sự phản chiếu. Xem bên dưới.


Trả lời bình luận nhạt nhẽo:

Tỷ lệ được đánh reflection:exceptiondấu cho 100000 lần lặp:

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

... đủ công bằng - nếu bạn dự đoán nó sẽ thất bại với xác suất có ít hơn ~ 1/47, thì hãy đi ngoại lệ.


Ở trên giả định rằng bạn đang chạy GetProperties()mỗi lần. Bạn có thể tăng tốc quá trình bằng cách lưu trữ kết quả của GetProperties()từng loại trong từ điển hoặc tương tự. Điều này có thể hữu ích nếu bạn kiểm tra cùng một nhóm các loại nhiều lần.


7
Tôi đồng ý và thích sự phản ánh trong công việc của tôi khi thích hợp. Lợi ích mà nó đạt được qua Thử / Bắt chỉ khi ném ngoại lệ. Vì vậy, những gì ai đó nên hỏi trước khi sử dụng sự phản chiếu ở đây - nó có khả năng là một cách nhất định? 90% hoặc thậm chí 75% thời gian, mã của bạn sẽ vượt qua? Sau đó, thử / bắt vẫn là tối ưu. Nếu nó ở trên không, hoặc có quá nhiều sự lựa chọn cho một khả năng, thì sự phản chiếu của bạn sẽ được chú ý.
nhạt nhẽo

@bland Chỉnh sửa câu trả lời.
dav_i

1
Cảm ơn, trông thực sự hoàn thành bây giờ.
nhạt nhẽo

@dav_i thật không công bằng khi so sánh cả hai vì cả hai cư xử khác nhau. Câu trả lời của Svick đầy đủ hơn.
nawfal

1
@dav_i Không, họ không thực hiện chức năng tương tự. Câu trả lời của Martijn kiểm tra nếu một thuộc tính tồn tại trên loại thời gian biên dịch thông thường trong C #, được khai báođộng (có nghĩa là nó bỏ qua kiểm tra an toàn thời gian biên dịch). Trong khi câu trả lời của Svick kiểm tra xem một thuộc tính có tồn tại trên một đối tượng thực sự năng động hay không , tức là một cái gì đó thực hiện IIDynamicMetaObjectProvider. Tôi hiểu động lực đằng sau câu trả lời của bạn và đánh giá cao nó. Thật công bằng khi trả lời như vậy.
nawfal

52

Có thể sử dụng sự phản chiếu?

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

2
Trích dẫn từ câu hỏi ". Tôi có thể thực hiện GetType () nhưng tôi muốn tránh điều đó hơn"
roundcrisis

Điều này không có nhược điểm giống như đề xuất của tôi sao? RouteValueDipedia sử dụng sự phản chiếu để có được các thuộc tính .
Steve Wilkes

12
Bạn chỉ có thể làm mà không cần Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i

Xin vui lòng xem câu trả lời của tôi để so sánh câu trả lời.
dav_i

3
Là một lót : ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Dàn diễn viên để gõ là cần thiết để làm cho trình biên dịch hài lòng về lambda.
MushinNoShin

38

Chỉ trong trường hợp nó giúp được ai đó:

Nếu phương thức GetDataThatLooksVerySimilarButNotTheSame()trả về, ExpandoObjectbạn cũng có thể chuyển sang IDictionarytrước khi kiểm tra.

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

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

3
Không chắc chắn tại sao câu trả lời này không có nhiều phiếu bầu hơn, bởi vì nó thực hiện chính xác những gì được yêu cầu (không có ngoại lệ hoặc phản ánh).
Wolfshead

7
@Wolfshead Câu trả lời này rất hay nếu bạn biết rằng đối tượng động của bạn là ExpandoObject hoặc một cái gì đó khác thực hiện IDadata <string, object> nhưng nếu nó xảy ra là một thứ khác, thì điều này sẽ thất bại.
Damian Powell

9

Hai giải pháp phổ biến cho việc này bao gồm thực hiện cuộc gọi và bắt RuntimeBinderException, sử dụng sự phản chiếu để kiểm tra cuộc gọi hoặc tuần tự hóa thành định dạng văn bản và phân tích cú pháp từ đó. Vấn đề với các ngoại lệ là chúng rất chậm, bởi vì khi được xây dựng, ngăn xếp cuộc gọi hiện tại được nối tiếp. Việc nối tiếp với JSON hoặc một cái gì đó tương tự phải chịu một hình phạt tương tự. Điều này để lại cho chúng ta sự phản chiếu nhưng nó chỉ hoạt động nếu đối tượng cơ bản thực sự là một POCO với các thành viên thực sự trên đó. Nếu đó là một trình bao bọc động xung quanh một từ điển, một đối tượng COM hoặc một dịch vụ web bên ngoài, thì sự phản chiếu sẽ không có ích.

Một giải pháp khác là sử dụng DynamicMetaObjectđể lấy tên thành viên khi DLR nhìn thấy chúng. Trong ví dụ dưới đây, tôi sử dụng một lớp tĩnh ( Dynamic) để kiểm tra Agetrường và hiển thị nó.

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;
    }
}

Nó chỉ ra rằng Dynamiteygói nuget đã làm điều này. ( nuget.org/packages/DOUNDitey )
Damian Powell

8

Câu trả lời của Denis khiến tôi nghĩ đến một giải pháp khác bằng cách sử dụng JsonObjects,

trình kiểm tra thuộc tính tiêu đề:

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

hoặc có thể tốt hơn:

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

ví dụ:

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

1
Có một cơ hội để biết những gì sai với câu trả lời này xin vui lòng?
Charles HETIER

Không biết tại sao điều này đã được bỏ phiếu xuống, làm việc tuyệt vời cho tôi. Tôi đã chuyển Vị ngữ cho mỗi thuộc tính vào một lớp trợ giúp và gọi phương thức Gọi để trả về một bool từ mỗi thuộc tính.
markp3rry

7

Vâng, tôi đã đối mặt với một vấn đề tương tự nhưng trong các bài kiểm tra đơn vị.

Sử dụng SharpTestsEx, bạn có thể kiểm tra nếu một tài sản tồn tại. Tôi sử dụng thử nghiệm bộ điều khiển này, vì đối tượng JSON là động, ai đó có thể thay đổi tên và quên thay đổi nó trong javascript hoặc một cái gì đó, vì vậy kiểm tra tất cả các thuộc tính khi viết bộ điều khiển sẽ tăng tính an toàn của tôi.

Thí dụ:

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

Bây giờ, sử dụng SharTestsEx:

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

Sử dụng cái này, tôi kiểm tra tất cả các thuộc tính hiện có bằng cách sử dụng "Should (). NotThrow ()".

Nó có thể nằm ngoài chủ đề, nhưng có thể hữu ích cho ai đó.


Cảm ơn, rất hữu ích. Sử dụng SharpTestsEx Tôi sử dụng dòng sau để kiểm tra giá trị của thuộc tính động:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen

2

Theo dõi câu trả lời của @karask, bạn có thể gói chức năng như một người trợ giúp như vậy:

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

2

Đối với tôi điều này hoạt động:

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;
    }
    catch
    {
        return true;
    }
}

nullkhông có nghĩa là tài sản không tồn tại
quetzalcoatl

Tôi biết nhưng nếu nó là null tôi không cần phải làm bất cứ điều gì với giá trị do đó đối với usecase của tôi thì không sao
Jester

0

Nếu bạn kiểm soát loại đang được sử dụng dưới dạng động, bạn không thể trả lại một tuple thay vì giá trị cho mỗi lần truy cập thuộc tính? Cái gì đó như...

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

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

Có thể là một triển khai ngây thơ, nhưng nếu bạn xây dựng một trong những nội bộ này mỗi lần và trả lại giá trị đó thay vì giá trị thực, bạn có thể kiểm tra Existsmọi quyền truy cập thuộc tính và sau đó nhấn Valuenếu nó không có giá trị default(T)(và không liên quan) nếu không.

Điều đó nói rằng, tôi có thể thiếu một số kiến ​​thức về cách hoạt động năng động và điều này có thể không phải là một gợi ý khả thi.


0

Trong trường hợp của tôi, tôi cần kiểm tra sự tồn tại của một phương thức với một tên cụ thể, vì vậy tôi đã sử dụng một giao diện cho điều đó

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

Ngoài ra, các giao diện có thể chứa nhiều hơn chỉ là các phương thức:

Các giao diện có thể chứa các phương thức, thuộc tính, sự kiện, bộ chỉ mục hoặc bất kỳ sự kết hợp nào của bốn loại thành viên đó.

Từ: Giao diện (Hướng dẫn lập trình C #)

Thanh lịch và không cần bẫy ngoại lệ hoặc chơi theo phản xạ ...


0

Tôi biết đây là bài viết thực sự cũ nhưng đây là một giải pháp đơn giản để làm việc với dynamicloại c#.

  1. có thể sử dụng phản xạ đơn giản để liệt kê các thuộc tính trực tiếp
  2. hoặc có thể sử dụng objectphương pháp kéo dài
  3. hoặc sử dụng GetAsOrDefault<int>phương thức để có được một đối tượng được gõ mạnh mới có giá trị nếu tồn tại hoặc mặc định nếu không tồn tại.
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 );
    }
}

0

Khi ExpandoObjectkế thừa, IDictionary<string, object>bạn có thể sử dụng kiểm tra sau

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Bạn có thể tạo một phương thức tiện ích để thực hiện kiểm tra này, điều đó sẽ làm cho mã sạch hơn và có thể sử dụng lại được


-1

Đây là cách khác:

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;
            }
        }
    }
}

2
bạn lấy ý tưởng từ đâu để kiểm tra các thuộc tính của JObject? Câu trả lời của bạn được giới hạn ở các đối tượng / lớp hiển thị IEnumerable trên các thuộc tính của chúng. Không được đảm bảo bởi dynamic. dynamictừ khóa là chủ đề rộng hơn nhiều . Go kiểm tra nếu bạn có thể kiểm tra Counttrong dynamic foo = new List<int>{ 1,2,3,4 }như thế
Quetzalcoatl
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.