Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAPH requires setting _nameHelper #47

Open
stokara opened this issue Jan 17, 2025 · 6 comments
Open

OAPH requires setting _nameHelper #47

stokara opened this issue Jan 17, 2025 · 6 comments

Comments

@stokara
Copy link

stokara commented Jan 17, 2025

Following the docs I tried:

[ObservableAsProperty]
public partial Type SearchTermType { get; }
...
this.WhenAnyValue(x => x.SelectedProperty)
            .WhereNotNull()
            .Select(p => p.Type)
            .ToProperty(this, x => x.SearchTermType);

but when getting to code that references SearchTermType it is null;

however, this code, referencing the generated code backing OAPH field does work:

 _searchTermTypeHelper = this.WhenAnyValue(x => x.SelectedProperty)
     .WhereNotNull()
     .Select(p => p.Type)
     .ToProperty(this, x => x.SearchTermType);

this is the using the approach I see in ReactiveGeneratorDemo.ViewModels.OaphViewModel

@CyLuGh
Copy link

CyLuGh commented Jan 17, 2025

Have you tried included the out parameter like this:

this.WhenAnyValue(x => x.SelectedProperty)
            .WhereNotNull()
            .Select(p => p.Type)
            .ToProperty(this, x => x.SearchTermType, out _searchTermTypeHelper);

@stokara
Copy link
Author

stokara commented Jan 17, 2025 via email

@CyLuGh
Copy link

CyLuGh commented Jan 17, 2025

The documentation in the readme and the example in the source (https://github.com/wieslawsoltes/ReactiveGenerator/blob/main/ReactiveGeneratorDemo/ViewModels/OaphViewModel.cs) don't seem to match.

@stokara
Copy link
Author

stokara commented Jan 17, 2025

suggestion: Creating a new extension method SetPropertyEx which would set the generated OAPH is not a bad idea as that is the way the Fody implementation works and would make migrating from Fody easier.

e.g.:

this.WhenAnyValue(x => x.SelectedProperty)
.WhereNotNull()
.Select(p => p.Type)
.ToPropertyEx(this, x => x.SearchTermType);

would functionally be:

this.WhenAnyValue(x => x.SelectedProperty)
.WhereNotNull()
.Select(p => p.Type)
.ToProperty(this, x => x.SearchTermType, out _searchTermTypeHelper);

@stokara
Copy link
Author

stokara commented Jan 19, 2025

Here is an example extension method .ToPropertyEx - but it uses reflection so it will impact performance.

public static class ReactiveUiExtensions {
    public static IDisposable ToPropertyEx<TObj, TRet>(
        this IObservable<TRet> observable,
        TObj target,
        Expression<Func<TObj, TRet>> property,
        TRet? initialValue = default,
        IScheduler? scheduler = null)
        where TObj : class, IReactiveObject {
        var propertyName = ((MemberExpression) property.Body).Member.Name;
        var fieldName = $"_{char.ToLower(propertyName[0])}{propertyName[1..]}Helper";

        var field = getFieldFromHierarchy(target.GetType(), fieldName);
        if (field == null) {
            throw new InvalidOperationException($"Field '{fieldName}' not found on type '{target.GetType().Name}'.");
        }

        var helper = observable.ToProperty(target, property!, initialValue: initialValue, scheduler: scheduler);
        field.SetValue(target, helper);
        return helper;
    }

    private static FieldInfo? getFieldFromHierarchy(Type? type, string fieldName) {
        while (type != null) {
            var field = type.GetField(fieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            if (field != null) return field;
            type = type.BaseType;
        }
        return null;
    }
}

@stokara
Copy link
Author

stokara commented Jan 19, 2025

The extension method I suggested above uses reflection and (at least for me) will often be called in the constructor, so I don't like it.

This can be avoided if a "getOaph" method was generated with the generated code, something like this:

        private ObservableAsPropertyHelper<T> GetOaph<T>(Expression<Func<T>> expression)
        {
            var memberExpression = (MemberExpression)expression.Body;
            var fieldName = memberExpression.Member.Name;

            return fieldName switch
            {
                nameof(TestBool) => (ObservableAsPropertyHelper<T>)(object)oaphTestBool,
                _ => throw new ArgumentException("Invalid field name", nameof(expression))
            };
        }

where the "fieldName switch" would contain all of the generated oaph variables

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants